We are evaluating a lifecycle architecture for a local macOS application and would appreciate guidance on supported public APIs.
Proposed architecture:
- A normally signed, non-root host application.
- One embedded XPC service with exactly one intended client.
- No application-created subprocesses inside the service.
- No privileged helper, persistent LaunchAgent, exported endpoints, private APIs, or reduced system security.
- One bounded operation per session. Failed or interrupted operations must not automatically be retried.
- Initial validation would use synthetic workloads only.
Our current evaluation target is macOS 15.6 on Apple silicon, using the macOS 15.5 SDK. Please identify any relevant deployment-version limitations.
The XPC overview (https://developer.apple.com/documentation/xpc) describes an embedded service as tied to its client's lifetime. We also understand that xpc_connection_cancel (https://developer.apple.com/documentation/xpc/xpc_connection_cancel(_:)) is asynchronous and non-preemptive; we are not treating connection cancellation as proof of service termination.
We need clarification on these points:
- Client death and service startup
Does the documented client-lifetime relationship cover client termination, including SIGKILL, during service startup before the first reply? Does it also cover an already running service that is blocked or stopped with SIGSTOP?
Which behavior is guaranteed, and which timing or failure cases are intentionally unspecified?
- Timeout while the client remains alive
What supported public mechanism should a normal application use to terminate its own unresponsive embedded service when an operation exceeds its deadline?
We want to avoid PID enumeration, PID-reuse races, broad process-group signaling, and exporting a full task-control port. Is there a supported identity-bound termination mechanism? If not, what architecture does Apple recommend?
A stopped or hung client remains alive, so we do not assume client-lifetime coupling handles that separate failure case.
- Observing termination
Which public notification reliably identifies the exit of the particular service instance, as distinct from connection invalidation or a missing reply?
Because launchd is the parent, the application cannot simply waitpid the service. Is reaping entirely launchd's responsibility, and what completion claim can the application legitimately make?
- Identity and required privileges
Which public signing requirements, launch constraints, sandbox settings, or entitlements are necessary for this architecture?
Please distinguish authentication before accepting work from constraints enforced before service code executes. We do not assume peer authentication also grants termination rights.
- Restart behavior
After interruption, cancellation, client exit, or service failure, under what circumstances can launchd or subsequent XPC activity start a replacement service?
What supported pattern prevents accidental resubmission or resumption of the same failed operation?
We are not asking for hard real-time guarantees during kernel failure, or claiming that forced termination proves callback completion or explicit memory zeroization.
If this combination of requirements is unsupported, identifying that boundary and the smallest supported architectural alternative would be a useful answer. Documentation references or a minimal public sample would be welcome.
Does the documented client-lifetime relationship cover client termination, including SIGKILL, during service startup before the first reply?
Yes. There’s an obvious race condition here, leaving potentially four potential outcomes:
- The service does not start.
- The service starts but doesn’t get any messages.
- The service starts but the connection is invalidated before it replies. (Note that this can only happen in the escaping case, see below, because messages to the service are serialised on the services queue.)
- The service starts and replies but the reply fails. There’s no explicit indication of this, although the service will see the connection be invalidated.
Does it also cover an already running service that is blocked or stopped with SIGSTOP?
SIGSTOP isn’t part of the XPC service world and you should not be using it in this context.
As to what happens if the service is blocked in, say, a read system call, that’s part of the story I covered above. XPC messages are delivered on a serial queue. If you block inside a message handler then no further messages can be delivered until you unblock. You either accept that as part of your design or you move any blocking work off that queue.
Remember that an XPC message handler is called with a reply block and it’s allowed to ‘escape’ that block. So it’s fine to capture the reply block, schedule the work on some other queue and then, when that’s date, call the reply block.
What supported public mechanism should a normal application use to terminate its own unresponsive embedded service when an operation exceeds its deadline?
XPC doesn’t have a timeout mechanism, but there’s nothing stopping you from implementing one yourself. You can do that within the service easily, using a variation of the escaping technique I described above. Doing it in the client is probably possible but it certainly isn’t a well-trodden path and I’m struggling to think of an approach that I actually like.
Which public notification reliably identifies the exit of the particular service instance … ?
XPC doesn’t really work that way. Clients work in terms of connections, not in terms of processes. (That’s why doing termination from the client is so tricky.)
Is reaping entirely launchd's responsibility … ?
Yes.
Which public signing requirements … are necessary for this architecture?
Nothing that’s XPC specific.
On Apple silicon all code must at least be ad hoc signed. Beyond that, you have to sign code based on your deployment channel.
Which … launch constraints … are necessary for this architecture?
Nothing that’s XPC specific.
Which … sandbox settings … are necessary for this architecture?
Nothing that’s XPC specific.
The service’s App Sandbox configuration is completely independent of the containing app, so you can have any combination of these (both not sandboxed, both sandboxed, app sandboxed and service not, service sandboxed and app not). And if they’re both sandboxed then they can have different sandbox configurations.
Remember that the Mac App Store requires that all code be sandboxed and does not, in general, allow temporary exception entitlements.
Which … entitlements are necessary for this architecture?
Nothing that’s XPC specific.
As with sandboxing, the entitlements for your XPC service are completely independent of the entitlements for the containing app.
After interruption, cancellation, client exit, or service failure, under what circumstances can launchd or subsequent XPC activity start a replacement service?
If the containing app exits then the system terminates the XPC service. If the user than relaunches the containing app the system starts a new XPC service. Conceptually this is completely independent of the previous instance.
You’ll see the same sort of thing if you find a way to launch a second instance of the containing app. Each instance gets its own completely separate instance of the XPC service.
By “service failure” I’m gonna presume that you mean that the service crashed. If so, the system will relaunch it on demand. The client will receive a connection interrupted error and, if the client retries, the system will relaunch the service.
Beyond that, the system may terminate the service if it considers it to be idle. If it does, the client gets a connection interrupted error. If the client then messages the service again, the system will restart it.
Keep in mind that the service lifetime isn’t tied to a specific connection. If the service cancels a connection, the client sees that as an interrupted error but that doesn’t in and of itself cause the system to terminate the process running the service.
What supported pattern prevents accidental resubmission or resumption of the same failed operation?
You need to carefully design your XPC protocol to avoid such problems.
In my experience folks generally don’t do that and don’t notice the resulting problems because XPC is in and of itself remarkably reliable. However, if you set up a test that randomly terminates an XPC service you’ll find that the final result is much worse than it should be.
This protocol design problem is exactly the same sort of thing you have to deal with when using the network, for example, when talking to a web service over HTTP, and you can use the same sorts of techniques to deal with it.
Finally, there are some things that I didn’t mention that you need to know about:
- Invalidation versus interruption
- XPC transactions, both implicit and explicit
- Send barriers and flow control
These are documented in the man pages, so you might already be up to speed on them. But, if not, lemme know and I’d be happy to elaborate.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"