LinuxContainer/LinuxProcess: Have wait return ExitStatus (#300)

Have wait return a struct containing the exit code and the time at which
the process actually exited. This is useful so higher level tools (like
container) don't need to craft a date themselves, and it will be more
accurate as it's taken in the guest directly after the process exits.
This commit is contained in:
Danny Canter authored and GitHub committed 2025-09-29 14:09:37 -04:00
1 parent 511dd59e19
commit 82f2d887dd
14 files changed
+100 -39

No files matched your search

+26
View File
@@ -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
}
@@ -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)
}
+3 -3
View File
@@ -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
@@ -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 {
@@ -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 {
@@ -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
}
@@ -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.
@@ -155,6 +157,7 @@ message WaitProcessRequest {
message WaitProcessResponse {
int32 exitCode = 1;
google.protobuf.Timestamp exited_at = 2;
}
message ResizeProcessRequest {
@@ -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
@@ -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 {
@@ -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(