From f867b04b4c25b1bce54764512ab847fce68cdecb Mon Sep 17 00:00:00 2001 From: Dmitry Kovba Date: Mon, 9 Feb 2026 17:21:46 -0800 Subject: [PATCH 1/7] Rename `SocketRelay` to `UnixSocketRelay` --- Sources/Containerization/UnixSocketRelay.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/Containerization/UnixSocketRelay.swift b/Sources/Containerization/UnixSocketRelay.swift index c93336d8..665b9bb9 100644 --- a/Sources/Containerization/UnixSocketRelay.swift +++ b/Sources/Containerization/UnixSocketRelay.swift @@ -23,7 +23,7 @@ import Synchronization package actor UnixSocketRelayManager { private let vm: any VirtualMachineInstance - private var relays: [String: SocketRelay] + private var relays: [String: UnixSocketRelay] private let q: DispatchQueue private let log: Logger? @@ -44,7 +44,7 @@ extension UnixSocketRelayManager { ) } - let socketRelay = try SocketRelay( + let relay = try UnixSocketRelay( port: port, socket: socket, vm: self.vm, @@ -53,8 +53,8 @@ extension UnixSocketRelayManager { ) do { - self.relays[socket.id] = socketRelay - try await socketRelay.start() + self.relays[socket.id] = relay + try await relay.start() } catch { self.relays.removeValue(forKey: socket.id) } @@ -77,7 +77,7 @@ extension UnixSocketRelayManager { } } -package final class SocketRelay: Sendable { +package final class UnixSocketRelay: Sendable { private let port: UInt32 private let configuration: UnixSocketConfiguration private let log: Logger? @@ -111,7 +111,7 @@ package final class SocketRelay: Sendable { } } -extension SocketRelay { +extension UnixSocketRelay { func start() async throws { switch configuration.direction { case .outOf: From bc7fc5171bef8d1bb6b1a0157ca7d20a58e89695 Mon Sep 17 00:00:00 2001 From: Dmitry Kovba Date: Mon, 9 Feb 2026 18:59:50 -0800 Subject: [PATCH 2/7] Keep throwing functions inside a do-catch block --- vminitd/Sources/vminitd/Server+GRPC.swift | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/vminitd/Sources/vminitd/Server+GRPC.swift b/vminitd/Sources/vminitd/Server+GRPC.swift index 9d28d83c..5edaabc1 100644 --- a/vminitd/Sources/vminitd/Server+GRPC.swift +++ b/vminitd/Sources/vminitd/Server+GRPC.swift @@ -198,16 +198,16 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvid "action": "\(request.action)", ]) - do { - let proxy = VsockProxy( - id: request.id, - action: request.action == .into ? .dial : .listen, - port: request.vsockPort, - path: URL(fileURLWithPath: request.guestPath), - udsPerms: request.guestSocketPermissions, - log: log - ) + let proxy = VsockProxy( + id: request.id, + action: request.action == .into ? .dial : .listen, + port: request.vsockPort, + path: URL(fileURLWithPath: request.guestPath), + udsPerms: request.guestSocketPermissions, + log: log + ) + do { try await proxy.start() try await state.add(proxy: proxy) } catch { From 846686549d9947775c092aa5bfc234d1d8bd51d3 Mon Sep 17 00:00:00 2001 From: Dmitry Kovba Date: Mon, 9 Feb 2026 19:00:07 -0800 Subject: [PATCH 3/7] Add logs at the info level --- vminitd/Sources/vminitd/Server+GRPC.swift | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/vminitd/Sources/vminitd/Server+GRPC.swift b/vminitd/Sources/vminitd/Server+GRPC.swift index 5edaabc1..1984cf4a 100644 --- a/vminitd/Sources/vminitd/Server+GRPC.swift +++ b/vminitd/Sources/vminitd/Server+GRPC.swift @@ -222,6 +222,14 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvid ) } + log.info( + "proxyVsock started", + metadata: [ + "id": "\(request.id)", + "port": "\(request.vsockPort)", + "guestPath": "\(request.guestPath)", + ]) + return .init() } @@ -250,6 +258,12 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvid ) } + log.info( + "stopVsockProxy completed", + metadata: [ + "id": "\(request.id)" + ]) + return .init() } From 53c66140a22936602572d09d380895d162cdd4c7 Mon Sep 17 00:00:00 2001 From: Dmitry Kovba Date: Tue, 10 Feb 2026 10:12:04 -0800 Subject: [PATCH 4/7] Wait for the relay to complete when both directions close --- .../Socket/BidirectionalRelay.swift | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Sources/ContainerizationOS/Socket/BidirectionalRelay.swift b/Sources/ContainerizationOS/Socket/BidirectionalRelay.swift index b74cf1eb..bb306f12 100644 --- a/Sources/ContainerizationOS/Socket/BidirectionalRelay.swift +++ b/Sources/ContainerizationOS/Socket/BidirectionalRelay.swift @@ -32,7 +32,14 @@ public final class BidirectionalRelay: Sendable { let source2: DispatchSourceRead } + private enum CompletionState { + case pending + case waiting(CheckedContinuation) + case completed + } + private let state: Mutex + private let completionState: Mutex // The buffers aren't used concurrently. private nonisolated(unsafe) let buffer1: UnsafeMutableBufferPointer @@ -56,6 +63,7 @@ public final class BidirectionalRelay: Sendable { self.queue = queue ?? DispatchQueue(label: "com.apple.containerization.bidirectional-relay") self.log = log self.state = Mutex(nil) + self.completionState = Mutex(.pending) let pageSize = Int(getpagesize()) self.buffer1 = UnsafeMutableBufferPointer.allocate(capacity: pageSize) @@ -134,6 +142,22 @@ public final class BidirectionalRelay: Sendable { } } + /// Waits for the relay to complete. + public func waitForCompletion() async { + await withCheckedContinuation { c in + completionState.withLock { state in + switch state { + case .pending: + state = .waiting(c) + case .waiting: + fatalError("waitForCompletion called multiple times") + case .completed: + c.resume() + } + } + } + } + private func fdCopyHandler( buffer: UnsafeMutableBufferPointer, source: DispatchSourceRead, @@ -253,5 +277,11 @@ public final class BidirectionalRelay: Sendable { ) close(fd1) close(fd2) + completionState.withLock { state in + if case .waiting(let c) = state { + c.resume() + } + state = .completed + } } } From 9e227b13cdc3dbcea00888ea2c63f56efc80a85b Mon Sep 17 00:00:00 2001 From: Dmitry Kovba Date: Tue, 10 Feb 2026 10:29:05 -0800 Subject: [PATCH 5/7] Move `UnixSocketRelayManager` into a separate file --- .../Containerization/UnixSocketRelay.swift | 56 -------------- .../UnixSocketRelayManager.swift | 75 +++++++++++++++++++ 2 files changed, 75 insertions(+), 56 deletions(-) create mode 100644 Sources/Containerization/UnixSocketRelayManager.swift diff --git a/Sources/Containerization/UnixSocketRelay.swift b/Sources/Containerization/UnixSocketRelay.swift index 665b9bb9..609720db 100644 --- a/Sources/Containerization/UnixSocketRelay.swift +++ b/Sources/Containerization/UnixSocketRelay.swift @@ -21,62 +21,6 @@ import Foundation import Logging import Synchronization -package actor UnixSocketRelayManager { - private let vm: any VirtualMachineInstance - private var relays: [String: UnixSocketRelay] - private let q: DispatchQueue - private let log: Logger? - - init(vm: any VirtualMachineInstance, log: Logger? = nil) { - self.vm = vm - self.relays = [:] - self.q = DispatchQueue(label: "com.apple.containerization.socket-relay") - self.log = log - } -} - -extension UnixSocketRelayManager { - func start(port: UInt32, socket: UnixSocketConfiguration) async throws { - guard self.relays[socket.id] == nil else { - throw ContainerizationError( - .invalidState, - message: "socket relay \(socket.id) already started" - ) - } - - let relay = try UnixSocketRelay( - port: port, - socket: socket, - vm: self.vm, - queue: self.q, - log: self.log - ) - - do { - self.relays[socket.id] = relay - try await relay.start() - } catch { - self.relays.removeValue(forKey: socket.id) - } - } - - func stop(socket: UnixSocketConfiguration) async throws { - guard let storedRelay = self.relays.removeValue(forKey: socket.id) else { - throw ContainerizationError( - .notFound, - message: "failed to stop socket relay" - ) - } - try storedRelay.stop() - } - - func stopAll() async throws { - for (_, relay) in self.relays { - try relay.stop() - } - } -} - package final class UnixSocketRelay: Sendable { private let port: UInt32 private let configuration: UnixSocketConfiguration diff --git a/Sources/Containerization/UnixSocketRelayManager.swift b/Sources/Containerization/UnixSocketRelayManager.swift new file mode 100644 index 00000000..bd6805a6 --- /dev/null +++ b/Sources/Containerization/UnixSocketRelayManager.swift @@ -0,0 +1,75 @@ +//===----------------------------------------------------------------------===// +// 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 Foundation +import Logging + +package actor UnixSocketRelayManager { + private let vm: any VirtualMachineInstance + private var relays: [String: UnixSocketRelay] + private let q: DispatchQueue + private let log: Logger? + + init(vm: any VirtualMachineInstance, log: Logger? = nil) { + self.vm = vm + self.relays = [:] + self.q = DispatchQueue(label: "com.apple.containerization.socket-relay") + self.log = log + } +} + +extension UnixSocketRelayManager { + func start(port: UInt32, socket: UnixSocketConfiguration) async throws { + guard self.relays[socket.id] == nil else { + throw ContainerizationError( + .invalidState, + message: "socket relay \(socket.id) already started" + ) + } + + let relay = try UnixSocketRelay( + port: port, + socket: socket, + vm: self.vm, + queue: self.q, + log: self.log + ) + + do { + self.relays[socket.id] = relay + try await relay.start() + } catch { + self.relays.removeValue(forKey: socket.id) + } + } + + func stop(socket: UnixSocketConfiguration) async throws { + guard let storedRelay = self.relays.removeValue(forKey: socket.id) else { + throw ContainerizationError( + .notFound, + message: "failed to stop socket relay" + ) + } + try storedRelay.stop() + } + + func stopAll() async throws { + for (_, relay) in self.relays { + try relay.stop() + } + } +} From 170a4deaf7997af442b0c9e2005f5a0f9d93bc53 Mon Sep 17 00:00:00 2001 From: Dmitry Kovba Date: Tue, 10 Feb 2026 10:36:47 -0800 Subject: [PATCH 6/7] Remove `self` where not needed --- .../Containerization/UnixSocketRelay.swift | 30 +++++++++---------- .../UnixSocketRelayManager.swift | 16 +++++----- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/Sources/Containerization/UnixSocketRelay.swift b/Sources/Containerization/UnixSocketRelay.swift index 609720db..6ce5ef67 100644 --- a/Sources/Containerization/UnixSocketRelay.swift +++ b/Sources/Containerization/UnixSocketRelay.swift @@ -51,7 +51,7 @@ package final class UnixSocketRelay: Sendable { } deinit { - self.state.withLock { $0.t?.cancel() } + state.withLock { $0.t?.cancel() } } } @@ -66,7 +66,7 @@ extension UnixSocketRelay { } func stop() throws { - try self.state.withLock { + try state.withLock { guard let t = $0.t else { throw ContainerizationError( .invalidState, @@ -92,7 +92,7 @@ extension UnixSocketRelay { } private func setupHostVsockDial() async throws { - let hostConn = self.configuration.destination + let hostConn = configuration.destination let socketType = try UnixType( path: hostConn.path, @@ -105,10 +105,10 @@ extension UnixSocketRelay { "listening on host UDS", metadata: [ "path": "\(hostConn.path)", - "vport": "\(self.port)", + "vport": "\(port)", ]) let connectionStream = try hostSocket.acceptStream(closeOnDeinit: false) - self.state.withLock { + state.withLock { $0.t = Task { do { for try await connection in connectionStream { @@ -128,11 +128,9 @@ extension UnixSocketRelay { } private func setupHostVsockListener() throws { - let hostPath = self.configuration.source - let port = self.port - let log = self.log + let hostPath = configuration.source - let listener = try self.vm.listen(self.port) + let listener = try vm.listen(port) log?.info( "listening on guest vsock", metadata: [ @@ -140,7 +138,7 @@ extension UnixSocketRelay { "vport": "\(port)", ]) - self.state.withLock { + state.withLock { $0.listener = listener $0.t = Task { do { @@ -149,12 +147,12 @@ extension UnixSocketRelay { try await self.handleGuestVsockConn( vsockConn: connection, hostConnectionPath: hostPath, - port: port, - log: log + port: self.port, + log: self.log ) } } catch { - log?.error("failed to setup relay between vsock \(port) and \(hostPath.path): \(error)") + self.log?.error("failed to setup relay between vsock \(self.port) and \(hostPath.path): \(error)") } } } @@ -226,11 +224,11 @@ extension UnixSocketRelay { let relay = BidirectionalRelay( fd1: hostFd, fd2: guestFd, - queue: self.q, - log: self.log + queue: q, + log: log ) - self.state.withLock { + state.withLock { $0.activeRelays[relayID] = relay } diff --git a/Sources/Containerization/UnixSocketRelayManager.swift b/Sources/Containerization/UnixSocketRelayManager.swift index bd6805a6..0aa4be91 100644 --- a/Sources/Containerization/UnixSocketRelayManager.swift +++ b/Sources/Containerization/UnixSocketRelayManager.swift @@ -34,7 +34,7 @@ package actor UnixSocketRelayManager { extension UnixSocketRelayManager { func start(port: UInt32, socket: UnixSocketConfiguration) async throws { - guard self.relays[socket.id] == nil else { + guard relays[socket.id] == nil else { throw ContainerizationError( .invalidState, message: "socket relay \(socket.id) already started" @@ -44,21 +44,21 @@ extension UnixSocketRelayManager { let relay = try UnixSocketRelay( port: port, socket: socket, - vm: self.vm, - queue: self.q, - log: self.log + vm: vm, + queue: q, + log: log ) do { - self.relays[socket.id] = relay + relays[socket.id] = relay try await relay.start() } catch { - self.relays.removeValue(forKey: socket.id) + relays.removeValue(forKey: socket.id) } } func stop(socket: UnixSocketConfiguration) async throws { - guard let storedRelay = self.relays.removeValue(forKey: socket.id) else { + guard let storedRelay = relays.removeValue(forKey: socket.id) else { throw ContainerizationError( .notFound, message: "failed to stop socket relay" @@ -68,7 +68,7 @@ extension UnixSocketRelayManager { } func stopAll() async throws { - for (_, relay) in self.relays { + for (_, relay) in relays { try relay.stop() } } From d43a47e66ed3bd1aaf90ae5e5513875737f5d50f Mon Sep 17 00:00:00 2001 From: Dmitry Kovba Date: Wed, 11 Feb 2026 12:37:38 -0800 Subject: [PATCH 7/7] Improve error handling --- vminitd/Sources/vminitd/Server+GRPC.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/vminitd/Sources/vminitd/Server+GRPC.swift b/vminitd/Sources/vminitd/Server+GRPC.swift index 1984cf4a..6d6d6e76 100644 --- a/vminitd/Sources/vminitd/Server+GRPC.swift +++ b/vminitd/Sources/vminitd/Server+GRPC.swift @@ -211,6 +211,7 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvid try await proxy.start() try await state.add(proxy: proxy) } catch { + try? await proxy.close() log.error( "proxyVsock", metadata: [