Use MainActor to ensure we're running on main queue (#515)

* Use MainActor to ensure we're running on main queue

...and to simplify the code.

* VZVirtualMachine.requestStop() is not asynchronous
This commit is contained in:
Nikolay Edigaryev 2023-06-07 15:06:33 +04:00 committed by GitHub
parent 546238d9df
commit 9016fcfdd4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 33 additions and 42 deletions

View File

@ -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() }
}
}
}

View File

@ -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<Void, Error>) 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)
}
}
}

View File

@ -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,