Subprocess ownership and timeout recovery on Intel macOS Monterey

Hi Quinn,

Following your recommendation by email, I’m posting this as a new thread. Thank you again for your guidance on Virtualization.framework.

This is a separate question about ordinary host-side subprocess ownership and timeout recovery, not VM cleanup.

Target environment:

  • Intel x86_64
  • macOS Monterey 12.7.6, build 21H1320
  • Python 3.13.15
  • Public, supported mechanisms; no root privileges, private APIs or kernel extensions

We are verifying a subprocess supervision component with this process structure:

Trusted caller process → runner → pytest → private helper → native subprocess

Each parent normally owns the consuming wait for its direct child. The private helper exclusively manages signalling and reaping of its native child. Outer processes do not signal saved helper/native PIDs.

Cancellation is cooperative, through channels and file/token delivery. The runner observes its fresh pytest child, drains stdout/stderr with bounded retention, and consumes its exact child’s status after exit is observed.

The runner, pytest and helper are launched in separate sessions. Consequently, signalling pytest’s process group would not cover the separately sessioned helper and its native child.

The unresolved failure case is:

  1. A fresh pytest invocation starts.
  2. Cancellation delivery fails, or delivery succeeds but the work does not finish.
  3. The runner continues observation and bounded output draining within the remaining deadline.
  4. The deadline expires while pytest or processes below it may still be alive.
  5. The runner reports an unresolved outcome and may exit.

The caller also has a bounded deadline and may return without consuming its runner child.

Closing capture pipes can cause EPIPE and disturb the child’s cleanup. Exiting a consuming parent also relinquishes its ability to collect that child’s eventual status. The outer caller does not automatically acquire wait rights over pytest or its descendants.

Our current policy retains affected files, preserves the original failure, and prevents subsequent tests from starting. We do not treat cancellation delivery, pipe EOF, successful signal delivery, or direct-child reap as proof that every descendant has stopped.

This prevents false success, but does not resolve responsibility for work that remains alive.

We have considered separating the caller’s response lifetime from the consuming owners’ lifetimes: the caller could return an unresolved response while both existing parents remain alive, draining output and collecting their direct children later. However, keeping them alive adds no termination authority and does not address a crash of either owner.

I am not asking for an absolute guarantee against arbitrary kernel failures, or a promise that SIGKILL always completes within a fixed time. I would like to understand the supported architectural approach on Monterey.

My questions are:

  1. Is retaining the existing consuming parents beyond the response deadline the appropriate pattern, or is there a supported macOS facility that provides a better ownership and recovery boundary?

  2. What safe termination authority should those owners retain over their fresh direct children? If process groups are part of the solution, what lifecycle rules are needed to avoid acting on a reused identifier, particularly after leader exit or reaping?

  3. Is there a supported way to manage the separately sessioned helper/native work if an intermediate parent exits? Or must each level independently retain its own supervision and cleanup responsibility?

  4. If the consuming owner itself crashes, is there a supported recovery mechanism for ordinary subprocesses? Would launchd or XPC materially help, and what limitations would remain for descendants in separate sessions?

  5. Which design constraint would you recommend changing: cooperative-only cancellation, the fixed lifetime of the runner, or the process topology? These are our current choices, not restrictions we assume macOS imposes.

Our practical goal is a bounded response while an identifiable owner remains responsible for unsettled work, with appropriate termination authority and an honest unresolved outcome when cleanup cannot be established.

The immediate qualification scope is only three simple tests, using shell bodies such as exit, kill -9 $$, and echo with ordinary exit statuses. They contain no intentional background work, pipelines or command substitutions. We would prefer a proportionate supported pattern rather than a new general supervision framework.

If a reduced example would help clarify the question, I can provide one.

Thank you for your time and guidance.

Answered by DTS Engineer in 909497022

I’ve been talking to a different developer about a similar issue and I think my reply this morning answers your question.

Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

What’s the high-level goal here? Are you trying to protect yourself from programming errors in these child processes? Or are you concerned that the child processes might deliberately do bad things, and thus you’re trying to manage them in a way to prevent that?

Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Hi Quinn,

Thanks — that distinction is helpful.

There are two separate trust boundaries in our design:

  • The host child process is a trusted executable that we build and sign. It owns a VZVirtualMachine. For this process, our concern is programming errors, hangs, crashes, and reliable cleanup, rather than deliberately malicious host code.
  • The guest runs potentially untrusted, AI-generated code. We intend to contain that code within the VM, with restricted resources and no configured host directory sharing or networking. We are not treating process groups or a watchdog as a security sandbox.

The immediate question is about the lifecycle of the VM when its host owner fails or must be terminated.

Our normal path is to request a controlled VM stop and collect the owner’s exit status. We also need to handle an unresponsive owner. If that exact owner process is forcibly terminated and its termination is confirmed, what can we rely on regarding the VM’s continued execution and the release of its virtualization resources?

In particular, is confirmed owner-process termination sufficient to establish that the guest can no longer execute, or is there another documented lifecycle mechanism or observation we should use?

We want to avoid claiming that collecting the host process’s exit status proves VM teardown unless that is a supported guarantee.

Thanks for helping us clarify the boundary.

I’ve been talking to a different developer about a similar issue and I think my reply this morning answers your question.

Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thanks, Quinn. Could you please share a link to the reply you mean? I’d like to make sure I’m reading the correct discussion.

Subprocess ownership and timeout recovery on Intel macOS Monterey
 
 
Q