Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
26 changes: 26 additions & 0 deletions Sources/Containerization/ExitStatus.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//===----------------------------------------------------------------------===//
// Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved.
//
// 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 Foundation

/// ExitStatus contains the exit code for a given container process,
/// as well as the timestamp at which it exited.
public struct ExitStatus: Sendable {
/// The exit code for the process.
public var exitCode: Int32
/// The timestamp when the process exited.
public var exitedAt: Date
}
2 changes: 1 addition & 1 deletion Sources/Containerization/LinuxContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -742,7 +742,7 @@ extension LinuxContainer {

/// Wait for the container to exit. Returns the exit code.
@discardableResult
public func wait(timeoutInSeconds: Int64? = nil) async throws -> Int32 {
public func wait(timeoutInSeconds: Int64? = nil) async throws -> ExitStatus {
let state = try self.state.withLock { try $0.startedState("wait") }
return try await state.process.wait(timeoutInSeconds: timeoutInSeconds)
}
Expand Down
6 changes: 3 additions & 3 deletions Sources/Containerization/LinuxProcess.swift
Original file line number Diff line number Diff line change
Expand Up @@ -343,15 +343,15 @@ extension LinuxProcess {

/// Wait on the process to exit with an optional timeout. Returns the exit code of the process.
@discardableResult
public func wait(timeoutInSeconds: Int64? = nil) async throws -> Int32 {
public func wait(timeoutInSeconds: Int64? = nil) async throws -> ExitStatus {
do {
let code = try await self.agent.waitProcess(
let exitStatus = try await self.agent.waitProcess(
id: self.id,
containerID: self.owningContainer,
timeoutInSeconds: timeoutInSeconds
)
await self.waitIoComplete()
return code
return exitStatus
} catch {
if error is ContainerizationError {
throw error
Expand Down
21 changes: 21 additions & 0 deletions Sources/Containerization/SandboxContext/SandboxContext.pb.swift
Original file line number Diff line number Diff line change
Expand Up @@ -476,9 +476,20 @@ public struct Com_Apple_Containerization_Sandbox_V3_WaitProcessResponse: Sendabl

public var exitCode: Int32 = 0

public var exitedAt: SwiftProtobuf.Google_Protobuf_Timestamp {
get {return _exitedAt ?? SwiftProtobuf.Google_Protobuf_Timestamp()}
set {_exitedAt = newValue}
}
/// Returns true if `exitedAt` has been explicitly set.
public var hasExitedAt: Bool {return self._exitedAt != nil}
/// Clears the value of `exitedAt`. Subsequent reads from it will return its default value.
public mutating func clearExitedAt() {self._exitedAt = nil}

public var unknownFields = SwiftProtobuf.UnknownStorage()

public init() {}

fileprivate var _exitedAt: SwiftProtobuf.Google_Protobuf_Timestamp? = nil
}

public struct Com_Apple_Containerization_Sandbox_V3_ResizeProcessRequest: Sendable {
Expand Down Expand Up @@ -1765,6 +1776,7 @@ extension Com_Apple_Containerization_Sandbox_V3_WaitProcessResponse: SwiftProtob
public static let protoMessageName: String = _protobuf_package + ".WaitProcessResponse"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "exitCode"),
2: .standard(proto: "exited_at"),
]

public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
Expand All @@ -1774,20 +1786,29 @@ extension Com_Apple_Containerization_Sandbox_V3_WaitProcessResponse: SwiftProtob
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularInt32Field(value: &self.exitCode) }()
case 2: try { try decoder.decodeSingularMessageField(value: &self._exitedAt) }()
default: break
}
}
}

public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
if self.exitCode != 0 {
try visitor.visitSingularInt32Field(value: self.exitCode, fieldNumber: 1)
}
try { if let v = self._exitedAt {
try visitor.visitSingularMessageField(value: v, fieldNumber: 2)
} }()
try unknownFields.traverse(visitor: &visitor)
}

public static func ==(lhs: Com_Apple_Containerization_Sandbox_V3_WaitProcessResponse, rhs: Com_Apple_Containerization_Sandbox_V3_WaitProcessResponse) -> Bool {
if lhs.exitCode != rhs.exitCode {return false}
if lhs._exitedAt != rhs._exitedAt {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
Expand Down
3 changes: 3 additions & 0 deletions Sources/Containerization/SandboxContext/SandboxContext.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ syntax = "proto3";

package com.apple.containerization.sandbox.v3;

import "google/protobuf/timestamp.proto";

// Context for interacting with a container's runtime environment.
service SandboxContext {
// Mount a filesystem.
Expand Down Expand Up @@ -155,6 +157,7 @@ message WaitProcessRequest {

message WaitProcessResponse {
int32 exitCode = 1;
google.protobuf.Timestamp exited_at = 2;
}

message ResizeProcessRequest {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Containerization/VirtualMachineAgent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public protocol VirtualMachineAgent: Sendable {
func startProcess(id: String, containerID: String?) async throws -> Int32
func signalProcess(id: String, containerID: String?, signal: Int32) async throws
func resizeProcess(id: String, containerID: String?, columns: UInt32, rows: UInt32) async throws
func waitProcess(id: String, containerID: String?, timeoutInSeconds: Int64?) async throws -> Int32
func waitProcess(id: String, containerID: String?, timeoutInSeconds: Int64?) async throws -> ExitStatus
func deleteProcess(id: String, containerID: String?) async throws
func closeProcessStdin(id: String, containerID: String?) async throws

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,11 @@ extension Vminitd: VirtualMachineAgent {
_ = try await client.resizeProcess(request)
}

public func waitProcess(id: String, containerID: String?, timeoutInSeconds: Int64? = nil) async throws -> Int32 {
public func waitProcess(
id: String,
containerID: String?,
timeoutInSeconds: Int64? = nil
) async throws -> ExitStatus {
let request = Com_Apple_Containerization_Sandbox_V3_WaitProcessRequest.with {
$0.id = id
if let containerID {
Expand All @@ -198,7 +202,7 @@ extension Vminitd: VirtualMachineAgent {
}
do {
let resp = try await client.waitProcess(request, callOptions: callOpts)
return resp.exitCode
return ExitStatus(exitCode: resp.exitCode, exitedAt: resp.exitedAt.date)
} catch {
if let err = error as? GRPCError.RPCTimedOut {
throw ContainerizationError(
Expand Down
30 changes: 15 additions & 15 deletions Sources/Integration/ProcessTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ extension IntegrationSuite {
let status = try await container.wait()
try await container.stop()

guard status == 0 else {
guard status.exitCode == 0 else {
throw IntegrationError.assert(msg: "process status \(status) != 0")
}
}
Expand All @@ -56,7 +56,7 @@ extension IntegrationSuite {
let status = try await container.wait()
try await container.stop()

guard status == 1 else {
guard status.exitCode == 1 else {
throw IntegrationError.assert(msg: "process status \(status) != 1")
}
}
Expand Down Expand Up @@ -105,7 +105,7 @@ extension IntegrationSuite {
let status = try await container.wait()
try await container.stop()

guard status == 0 else {
guard status.exitCode == 0 else {
throw IntegrationError.assert(msg: "process status \(status) != 1")
}

Expand Down Expand Up @@ -140,7 +140,7 @@ extension IntegrationSuite {
group.addTask {
try await exec.start()
let status = try await exec.wait()
if status != 0 {
if status.exitCode != 0 {
throw IntegrationError.assert(msg: "process status \(status) != 0")
}
try await exec.delete()
Expand Down Expand Up @@ -185,7 +185,7 @@ extension IntegrationSuite {

try await exec.start()
let status = try await exec.wait()
if status != 0 {
if status.exitCode != 0 {
throw IntegrationError.assert(msg: "process status \(status) != 0")
}

Expand All @@ -203,7 +203,7 @@ extension IntegrationSuite {
try await exec.start()

let status = try await exec.wait()
if status != 0 {
if status.exitCode != 0 {
throw IntegrationError.assert(msg: "process \(idx) status \(status) != 0")
}

Expand Down Expand Up @@ -247,7 +247,7 @@ extension IntegrationSuite {
var status = try await container.wait()
try await container.stop()

guard status == 0 else {
guard status.exitCode == 0 else {
throw IntegrationError.assert(msg: "process status \(status) != 0")
}

Expand All @@ -271,7 +271,7 @@ extension IntegrationSuite {
status = try await container.wait()
try await container.stop()

guard status == 0 else {
guard status.exitCode == 0 else {
throw IntegrationError.assert(msg: "process status \(status) != 0")
}

Expand All @@ -295,7 +295,7 @@ extension IntegrationSuite {
status = try await container.wait()
try await container.stop()

guard status == 0 else {
guard status.exitCode == 0 else {
throw IntegrationError.assert(msg: "process status \(status) != 0")
}

Expand Down Expand Up @@ -340,7 +340,7 @@ extension IntegrationSuite {
let status = try await container.wait()
try await container.stop()

guard status == 0 else {
guard status.exitCode == 0 else {
throw IntegrationError.assert(msg: "process status \(status) != 0")
}

Expand Down Expand Up @@ -374,7 +374,7 @@ extension IntegrationSuite {
let status = try await container.wait()
try await container.stop()

guard status == 0 else {
guard status.exitCode == 0 else {
throw IntegrationError.assert(msg: "process status \(status) != 0")
}

Expand Down Expand Up @@ -409,7 +409,7 @@ extension IntegrationSuite {
let status = try await container.wait()
try await container.stop()

guard status == 0 else {
guard status.exitCode == 0 else {
throw IntegrationError.assert(msg: "process status \(status) != 0")
}

Expand Down Expand Up @@ -439,7 +439,7 @@ extension IntegrationSuite {
let status = try await container.wait()
try await container.stop()

guard status == 0 else {
guard status.exitCode == 0 else {
throw IntegrationError.assert(msg: "process status \(status) != 0")
}
let expected = "foo-bar"
Expand Down Expand Up @@ -468,7 +468,7 @@ extension IntegrationSuite {
let status = try await container.wait()
try await container.stop()

guard status == 0 else {
guard status.exitCode == 0 else {
throw IntegrationError.assert(msg: "process status \(status) != 0")
}

Expand Down Expand Up @@ -496,7 +496,7 @@ extension IntegrationSuite {
let status = try await container.wait()
try await container.stop()

guard status == 0 else {
guard status.exitCode == 0 else {
throw IntegrationError.assert(msg: "process status \(status) != 0")
}
let expected = "Hello from test"
Expand Down
16 changes: 8 additions & 8 deletions Sources/Integration/VMTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ extension IntegrationSuite {
let status = try await container.wait()
try await container.stop()

guard status == 0 else {
guard status.exitCode == 0 else {
throw IntegrationError.assert(msg: "process status \(status) != 0")
}

Expand Down Expand Up @@ -96,7 +96,7 @@ extension IntegrationSuite {

let status = try await t.value

guard status == 0 else {
guard status.exitCode == 0 else {
throw IntegrationError.assert(msg: "process status \(status) != 0")
}

Expand Down Expand Up @@ -160,7 +160,7 @@ extension IntegrationSuite {
let status = try await container.wait()
try await container.stop()

guard status == 0 else {
guard status.exitCode == 0 else {
throw IntegrationError.assert(msg: "process status \(status) != 0")
}
}
Expand Down Expand Up @@ -195,7 +195,7 @@ extension IntegrationSuite {
let status = try await container.wait()
try await container.stop()

guard status == 0 else {
guard status.exitCode == 0 else {
throw IntegrationError.assert(msg: "process status \(status) != 0")
}

Expand Down Expand Up @@ -234,7 +234,7 @@ extension IntegrationSuite {

// Wait for completion
let status = try await container.wait()
guard status == 0 else {
guard status.exitCode == 0 else {
throw IntegrationError.assert(msg: "process status \(status) != 0")
}

Expand Down Expand Up @@ -277,7 +277,7 @@ extension IntegrationSuite {

// Wait for completion
var status = try await container.wait()
guard status == 0 else {
guard status.exitCode == 0 else {
throw IntegrationError.assert(msg: "process status \(status) != 0")
}
try await container.stop()
Expand All @@ -288,7 +288,7 @@ extension IntegrationSuite {

// Wait for completion.. again.
status = try await container.wait()
guard status == 0 else {
guard status.exitCode == 0 else {
throw IntegrationError.assert(msg: "process status \(status) != 0")
}

Expand Down Expand Up @@ -332,7 +332,7 @@ extension IntegrationSuite {

let status = try await container.wait()
try await container.stop()
guard status == 0 else {
guard status.exitCode == 0 else {
throw IntegrationError.assert(msg: "process status \(status) != 0")
}

Expand Down
2 changes: 1 addition & 1 deletion vminitd/Sources/vminitd/ManagedContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ extension ManagedContainer {
return try await ProcessSupervisor.default.start(process: proc)
}

func wait(execID: String) async throws -> Int32 {
func wait(execID: String) async throws -> ManagedProcess.ExitStatus {
let proc = try self.getExecOrInit(execID: execID)
return await proc.wait()
}
Expand Down
Loading