diff --git a/Sources/tart/Commands/Run.swift b/Sources/tart/Commands/Run.swift index dc0833b..b3cd6c8 100644 --- a/Sources/tart/Commands/Run.swift +++ b/Sources/tart/Commands/Run.swift @@ -427,7 +427,7 @@ struct Run: AsyncParsableCommand { Task { try await vm!.virtualMachine.stop() } } Button("Request Stop") { - Task { try await vm!.virtualMachine.requestStop() } + Task { try vm!.virtualMachine.requestStop() } } } } diff --git a/Sources/tart/VM+Recovery.swift b/Sources/tart/VM+Recovery.swift index 18c18b4..342ca8a 100644 --- a/Sources/tart/VM+Recovery.swift +++ b/Sources/tart/VM+Recovery.swift @@ -5,34 +5,26 @@ import Dynamic // Kudos to @saagarjha's VirtualApple for finding about _VZVirtualMachineStartOptions extension VZVirtualMachine { - @available(macOS 12, *) + @MainActor @available(macOS 12, *) func start(_ recovery: Bool) async throws { if !recovery { // just use the regular API - return try await withCheckedThrowingContinuation { continuation in - DispatchQueue.main.async { - self.start(completionHandler: { result in - continuation.resume(with: result) - }) - } - } + return try await self.start() } // use some private stuff only for recovery return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - DispatchQueue.main.async { - let handler: @convention(block) (_ result: Any?) -> Void = { result in - if let error = result as? Error { - continuation.resume(throwing: error) - } else { - continuation.resume(returning: ()) - } + let handler: @convention(block) (_ result: Any?) -> Void = { result in + if let error = result as? Error { + continuation.resume(throwing: error) + } else { + continuation.resume(returning: ()) } - // dynamic magic - let options = Dynamic._VZVirtualMachineStartOptions() - options.bootMacOSRecovery = recovery - Dynamic(self)._start(withOptions: options, completionHandler: handler) } + // dynamic magic + let options = Dynamic._VZVirtualMachineStartOptions() + options.bootMacOSRecovery = recovery + Dynamic(self)._start(withOptions: options, completionHandler: handler) } } } diff --git a/Sources/tart/VM.swift b/Sources/tart/VM.swift index b0cefef..5579627 100644 --- a/Sources/tart/VM.swift +++ b/Sources/tart/VM.swift @@ -223,24 +223,9 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject { func run(_ recovery: Bool) async throws { try network.run(sema) - let startTask = DispatchQueue.main.sync { - Task { - if #available(macOS 13, *) { - // new API introduced in Ventura - let startOptions = VZMacOSVirtualMachineStartOptions() - startOptions.startUpFromMacOSRecovery = recovery - try await virtualMachine.start(options: startOptions) - } else { - // use method that also available on Monterey - try await virtualMachine.start(recovery) - } - } - } - - try await withTaskCancellationHandler(operation: { - // Await on VZVirtualMachine.start() result - _ = try await startTask.value + try await start(recovery) + await withTaskCancellationHandler(operation: { // Wait for the VM to finish running // or for the exit condition sema.wait() @@ -249,16 +234,30 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject { }) if Task.isCancelled { - DispatchQueue.main.sync { - Task { - try await self.virtualMachine.stop() - } - } + try await stop() } try await network.stop() } + @MainActor + private func start(_ recovery: Bool) async throws { + if #available(macOS 13, *) { + // new API introduced in Ventura + let startOptions = VZMacOSVirtualMachineStartOptions() + startOptions.startUpFromMacOSRecovery = recovery + try await virtualMachine.start(options: startOptions) + } else { + // use method that also available on Monterey + try await virtualMachine.start(recovery) + } + } + + @MainActor + private func stop() async throws { + try await self.virtualMachine.stop() + } + static func craftConfiguration( diskURL: URL, nvramURL: URL,