Run ErrorReportingTask operations on the caller's actor (#1324)

VZVirtualMachine asserts that it is used on the queue it was created with,
which for tart is the main queue. Since #1262 replaced the unstructured Task
in "tart run"'s SIGUSR2 handler with ErrorReportingTask, that assertion fails:
Task.init carries @_inheritActorContext, but ErrorReportingTask.init did not,
so an operation written inside MainActor-isolated Run.runOnMainThread() is formed
in a nonisolated init and runs on the cooperative pool, not the main queue.

The result is that asking a VM to stop gracefully kills it instead. Sending
SIGUSR2, which #842 hooked to requestStop() for exactly this purpose, crashes
the process:

    Thread 1  queue: com.apple.root.default-qos.cooperative
      _dispatch_assert_queue_fail
      dispatch_assert_queue
      -[VZVirtualMachine requestStopWithError:]
      closure in Run.runOnMainThread()
      closure in ErrorReportingTask.init(_:operation:)

The guest then loses power without a chance to flush, and on a Linux guest
with ext4's default delayed allocation that discards whatever had not been
written back yet. The same applies to the requestStop() in
applicationShouldTerminate(), i.e. closing the window of a VM run with a GUI.

Give the operation the same @_inheritActorContext that Task.init has, so that
wrapping a call in ErrorReportingTask no longer changes where it runs.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Sam Estep
2026-09-09 15:05:42 -07:00
committed by GitHub
co-authored by Claude Opus 5
parent cdb3579c79
commit 9bb2af2434
+4 -1
View File
@@ -6,8 +6,11 @@ import Foundation
struct ErrorReportingTask {
let task: Task<Void, Never>
// Inherit the caller's actor context, exactly as Task.init does. Without this, an
// operation written inside a @MainActor function runs on the cooperative pool
// rather than the main queue, trapping in callees that assert their queue.
@discardableResult
init(_ context: String, operation: @escaping @Sendable () async throws -> Void) {
init(_ context: String, @_inheritActorContext operation: @escaping @Sendable () async throws -> Void) {
task = Task {
do {
try await operation()