mirror of
https://github.com/apple/container.git
synced 2026-10-13 09:39:18 +00:00
Upgrade grpc-swift-nio-transport to 2.9.0 and remove HTTP2ConnectBuff… (#778)
- Fixes #776 - Release 2.9.0 of `grpc-swift-nio-transport` fixes the HTTP/2 initialization race that required a `HTTP2ConnectBufferingHandler` for the vminitd API. This upgrades the dependency and eliminates the workaround.
This commit is contained in:
1 parent
bd709ba8cb
commit
7aa4e723f6
8 files changed
+35
-120
No files matched your search
+3
-3
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"originHash" : "5d4a569160adc023c31092ec813aeb5f7e7ac67ed853dfeb76f9964d10109bca",
|
||||
"originHash" : "2b73182b4d0f81b61ee24d8e615d350d28301517488d944fafddde15bc854cdc",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "async-http-client",
|
||||
@@ -24,8 +24,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
@@ -43,7 +43,7 @@ let package = Package(
|
||||
.package(url: "https://github.com/apple/swift-collections.git", from: "1.1.4"),
|
||||
.package(url: "https://github.com/apple/swift-crypto.git", from: "3.0.0"),
|
||||
.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/apple/swift-protobuf.git", from: "1.36.0"),
|
||||
.package(url: "https://github.com/apple/swift-nio.git", from: "2.80.0"),
|
||||
|
||||
@@ -261,7 +261,7 @@ extension CHVirtualMachineInstance: VirtualMachineInstance {
|
||||
try await chCall { try await self.client.vmBoot() }
|
||||
|
||||
let fh = try await self.dialVminitdWithRetries()
|
||||
let agent = try Vminitd(connection: fh, group: self.group)
|
||||
let agent = try await Vminitd(connection: fh, group: self.group)
|
||||
await self.timeSyncer.start(context: agent)
|
||||
|
||||
for ext in self.config.extensions.compactMap({ $0 as? any CHInstanceExtension }) {
|
||||
@@ -377,7 +377,7 @@ extension CHVirtualMachineInstance: VirtualMachineInstance {
|
||||
baseSocket: self.workDir.appendingPathComponent("vsock.sock"),
|
||||
port: Vminitd.port
|
||||
)
|
||||
return try Vminitd(connection: fh, group: self.group)
|
||||
return try await Vminitd(connection: fh, group: self.group)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Copyright © 2026 Apple Inc. and the Containerization project authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import ContainerizationError
|
||||
import GRPCCore
|
||||
import GRPCNIOTransportCore
|
||||
import NIOCore
|
||||
import NIOPosix
|
||||
|
||||
/// Buffers incoming bytes until the full gRPC HTTP/2 pipeline is configured, then replays them.
|
||||
///
|
||||
/// This prevents the race condition where the vminitd server's initial HTTP/2 SETTINGS frame
|
||||
/// arrives and is discarded before `configureGRPCClientPipeline` has finished installing
|
||||
/// `ClientConnectionHandler`.
|
||||
///
|
||||
/// The handler is added via `ClientBootstrap.channelInitializer`, which runs before
|
||||
/// `registerAlreadyConfigured0` adds the fd to epoll/kqueue — guaranteeing it is in place
|
||||
/// before any bytes can arrive on the socket.
|
||||
///
|
||||
/// When `NIOHTTP2Handler` is added to the pipeline (inside `configureGRPCClientPipeline`), its
|
||||
/// `handlerAdded` fires an outbound flush (the HTTP/2 client preface). We intercept that flush
|
||||
/// and schedule a deferred removal via the event loop. Because `configureGRPCClientPipeline` runs
|
||||
/// as a single synchronous event loop task, the deferred removal is guaranteed to run after that
|
||||
/// entire task completes — i.e., after `ClientConnectionHandler` is also in the pipeline.
|
||||
/// Buffered bytes are replayed atomically as part of the pipeline removal.
|
||||
|
||||
// FIXME: This handler is needed until the swift GRPC libraries offers us a way to create a
|
||||
// client transport from an existing fd. Remove this type when such an API exists.
|
||||
public final class HTTP2ConnectBufferingHandler: ChannelDuplexHandler, RemovableChannelHandler {
|
||||
public typealias InboundIn = ByteBuffer
|
||||
public typealias InboundOut = ByteBuffer
|
||||
public typealias OutboundIn = ByteBuffer
|
||||
public typealias OutboundOut = ByteBuffer
|
||||
|
||||
private var removalScheduled = false
|
||||
private var bufferedReads: [NIOAny] = []
|
||||
|
||||
public init() {}
|
||||
|
||||
public func channelRead(context: ChannelHandlerContext, data: NIOAny) {
|
||||
bufferedReads.append(data)
|
||||
}
|
||||
|
||||
public func channelReadComplete(context: ChannelHandlerContext) {
|
||||
// Suppress while buffering; a single readComplete is emitted after replay.
|
||||
}
|
||||
|
||||
public func flush(context: ChannelHandlerContext) {
|
||||
if !removalScheduled {
|
||||
removalScheduled = true
|
||||
// Defer removal to the next event loop task. configureGRPCClientPipeline runs as a
|
||||
// single synchronous event loop task, so this deferred task is guaranteed to run
|
||||
// after that whole task completes (including ClientConnectionHandler being added).
|
||||
context.eventLoop.assumeIsolatedUnsafeUnchecked().execute {
|
||||
context.pipeline.syncOperations.removeHandler(self, promise: nil)
|
||||
}
|
||||
}
|
||||
context.flush()
|
||||
}
|
||||
|
||||
public 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)
|
||||
}
|
||||
|
||||
public func channelInactive(context: ChannelHandlerContext) {
|
||||
bufferedReads.removeAll()
|
||||
context.fireChannelInactive()
|
||||
}
|
||||
}
|
||||
@@ -191,7 +191,7 @@ extension VZVirtualMachineInstance: VirtualMachineInstance {
|
||||
|
||||
try await self.vm.start(queue: self.queue)
|
||||
|
||||
let agent = try Vminitd(
|
||||
let agent = try await Vminitd(
|
||||
connection: try await self.vm.waitForAgent(queue: self.queue),
|
||||
group: self.group
|
||||
)
|
||||
@@ -260,7 +260,7 @@ extension VZVirtualMachineInstance: VirtualMachineInstance {
|
||||
port: Vminitd.port
|
||||
)
|
||||
let handle = try conn.dupHandle()
|
||||
return try Vminitd(connection: handle, group: self.group)
|
||||
return try await Vminitd(connection: handle, group: self.group)
|
||||
} catch {
|
||||
if let err = error as? ContainerizationError {
|
||||
throw err
|
||||
|
||||
@@ -20,9 +20,8 @@ import ContainerizationOCI
|
||||
import ContainerizationOS
|
||||
import Foundation
|
||||
import GRPCCore
|
||||
import GRPCNIOTransportCore
|
||||
import NIOCore
|
||||
import NIOPosix
|
||||
import GRPCNIOTransportHTTP2
|
||||
import NIO
|
||||
|
||||
/// A remote connection into the vminitd Linux guest agent via a port (vsock).
|
||||
/// Used to modify the runtime environment of the Linux sandbox.
|
||||
@@ -34,18 +33,24 @@ public struct Vminitd: Sendable {
|
||||
public let grpcClient: GRPCClient<HTTP2ClientTransport.WrappedChannel>
|
||||
private let connectionTask: Task<Void, Error>
|
||||
|
||||
public init(connection: FileHandle, group: any EventLoopGroup) throws {
|
||||
let channel = try ClientBootstrap(group: group)
|
||||
.channelInitializer { channel in
|
||||
channel.eventLoop.makeCompletedFuture(withResultOf: {
|
||||
try channel.pipeline.syncOperations.addHandler(HTTP2ConnectBufferingHandler())
|
||||
})
|
||||
public init(connection: FileHandle, group: any EventLoopGroup) async throws {
|
||||
let transport = try await HTTP2ClientTransport.WrappedChannel.wrapping(
|
||||
config: .defaults { $0.connection.maxIdleTime = nil },
|
||||
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(connection.fileDescriptor)
|
||||
.whenFailure { error in
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
.withConnectedSocket(connection.fileDescriptor).wait()
|
||||
let transport = HTTP2ClientTransport.WrappedChannel.wrapping(
|
||||
channel: channel,
|
||||
config: .defaults { $0.connection.maxIdleTime = nil }
|
||||
)
|
||||
}
|
||||
let grpcClient = GRPCClient(transport: transport)
|
||||
self.grpcClient = grpcClient
|
||||
self.client = Com_Apple_Containerization_Sandbox_V3_SandboxContext.Client(wrapping: self.grpcClient)
|
||||
|
||||
@@ -1733,7 +1733,7 @@ extension IntegrationSuite {
|
||||
try await assertExec(container, id: "create-fifo", cmd: "mkfifo /tmp/test-fifo")
|
||||
|
||||
let vsock = try await container.dialVsock(port: 1024)
|
||||
let vminitd = try Vminitd(connection: vsock, group: Self.eventLoop)
|
||||
let vminitd = try await Vminitd(connection: vsock, group: Self.eventLoop)
|
||||
|
||||
let root = URL(filePath: container.root)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"originHash" : "6ccceb47b6a402e9ac07d23204ec7f4792823b22b96275cd67f8531787a60c04",
|
||||
"originHash" : "264b211a5ea74fa24ced86faade5901700722c925484a26379cc4a6b40083c6c",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "async-http-client",
|
||||
@@ -24,8 +24,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -150,8 +150,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-nio.git",
|
||||
"state" : {
|
||||
"revision" : "4e8f4b1c9adaa59315c523540c1ff2b38adc20a9",
|
||||
"version" : "2.87.0"
|
||||
"revision" : "cd3e1152083706d77b223fb29110e590efcc70c0",
|
||||
"version" : "2.101.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -168,8 +168,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-nio-http2.git",
|
||||
"state" : {
|
||||
"revision" : "5e9e99ec96c53bc2c18ddd10c1e25a3cd97c55e5",
|
||||
"version" : "1.38.0"
|
||||
"revision" : "61d1b44f6e4e118792be1cff88ee2bc0267c6f9a",
|
||||
"version" : "1.44.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user