diff --git a/Sources/tart/Commands/Run.swift b/Sources/tart/Commands/Run.swift index 2e89590..70d5515 100644 --- a/Sources/tart/Commands/Run.swift +++ b/Sources/tart/Commands/Run.swift @@ -514,7 +514,7 @@ struct Run: AsyncParsableCommand { } if #available(macOS 14, *) { - Task { + ErrorReportingTask("Failed to run control socket") { try await ControlSocket(vmDir.controlSocketURL).run() } } @@ -586,7 +586,7 @@ struct Run: AsyncParsableCommand { signal(SIGUSR2, SIG_IGN) let sigusr2Src = DispatchSource.makeSignalSource(signal: SIGUSR2) sigusr2Src.setEventHandler { - Task { + ErrorReportingTask("Failed to request guest OS to stop") { print("Requesting guest OS to stop...") try vm!.virtualMachine.requestStop() } @@ -798,13 +798,13 @@ struct MainApp: App { CommandGroup(replacing: .appInfo) { AboutTart(config: vm!.config) } CommandMenu("Control") { Button("Start") { - Task { try await vm!.virtualMachine.start() } + ErrorReportingTask("Failed to start VM") { try await vm!.virtualMachine.start() } } Button("Stop") { - Task { try await vm!.virtualMachine.stop() } + ErrorReportingTask("Failed to stop VM") { try await vm!.virtualMachine.stop() } } Button("Request Stop") { - Task { try vm!.virtualMachine.requestStop() } + ErrorReportingTask("Failed to request VM stop") { try vm!.virtualMachine.requestStop() } } if #available(macOS 14, *) { if (MainApp.suspendable) { diff --git a/Sources/tart/Utils.swift b/Sources/tart/Utils.swift index ddb54bb..1f2dc70 100644 --- a/Sources/tart/Utils.swift +++ b/Sources/tart/Utils.swift @@ -1,5 +1,23 @@ import Foundation +// A fire-and-forget task that reports any thrown error to stderr. An unstructured +// Task spawned from a synchronous context (a signal handler, a SwiftUI action) has +// no parent to propagate its error to, so we report it here instead of dropping it. +struct ErrorReportingTask { + let task: Task + + @discardableResult + init(_ context: String, operation: @escaping @Sendable () async throws -> Void) { + task = Task { + do { + try await operation() + } catch { + fputs("\(context): \(error)\n", stderr) + } + } + } +} + extension Collection { subscript (safe index: Index) -> Element? { indices.contains(index) ? self[index] : nil