fix: reply to unknown XPC routes and make request timeout cancellable#1862
fix: reply to unknown XPC routes and make request timeout cancellable#1862radheradhe01 wants to merge 1 commit into
Conversation
…ncel the waiter
### Problem
Two related issues in the XPC request path:
1. **Unknown route hangs the client.** `XPCServer` dispatches with `if let handler = routes[route] { ... }` and **no `else`**. An unknown route produces no reply at all, so the client blocks until its timeout (or forever, if it sent none).
2. **The client timeout is illusory.** `XPCClient.send` races a timeout task against the reply task in a `withThrowingTaskGroup`. The reply task uses `withCheckedThrowingContinuation`, which is **not** cancellation-aware. When the timeout fires, the group tears down and awaits the reply task — which only completes when the daemon actually replies. Against a live-but-hung daemon the "timeout" never returns.
### Fix
- `XPCServer`: add the `else` branch and reply with an `invalidArgument` error for unknown routes.
- `XPCClient`: wrap the reply task in `withTaskCancellationHandler` and bridge the continuation through a small resume-once box (`XPCReplyBox`). On cancellation the continuation is resumed promptly; a late XPC reply becomes a no-op. The box guarantees the continuation is resumed exactly once.
### Notes
The connection is left intact on timeout; only the pending request is abandoned.
|
I validated this implementation on current Apple main as the first standalone commit of #1935, preserving the original author and code unchanged. The follow-up commit adds deterministic coverage for timeout return, caller cancellation, late replies, client reuse after timeout, cancellation before continuation storage, and unknown-route replies. If preferred, those tests can be folded into this PR; #1935's separate functional delta is the shorter root-help plugin-discovery policy. |
Import apple#1862 unchanged on top of current Apple main, preserving the original author and implementation.
Exercise the timeout, caller-cancellation, late-reply, client-reuse, and unknown-route paths introduced by apple#1862.
Use a one-second API health deadline for container --help, container help, and bare container while preserving the normal ten-second plugin dispatch deadline. Fall back to built-in help when the daemon is unavailable. Depends on the cancellable XPC request handling from apple#1862 and supersedes apple#1838 for apple#1459.
|
I can confirm the illusory-timeout half of this from an independent reproduction against a stock 1.1.0 install, hit while building on ContainerAPIClient.
Two notes from reading the change:
Worth cross-linking #1927: with this change that cp fails at its 300 second budget with a clear timeout error instead of hanging until reboot, independent of the guest-side fix in apple/containerization#799. I built the same shape of fix (a resume-once race around the non-cancellable send) into my own client layer and it has held up. Good to see it proposed upstream. |
Type of Change
Motivation and Context
Two related issues in the XPC request path:
XPCServerdispatches withif let handler = routes[route] { ... }and noelse. An unknown route produces no reply at all, so the client blocks until its timeout (or forever, if it sent none).XPCClient.sendraces a timeout task against the reply task in awithThrowingTaskGroup. The reply task useswithCheckedThrowingContinuation, which is not cancellation-aware. When the timeout fires, the group tears down and awaits the reply task — which only completes when the daemon actually replies. Against a live-but-hung daemon the "timeout" never returns.This change:
XPCServer: adds theelsebranch and replies with aninvalidArgumenterror for unknown routes.XPCClient: wraps the reply task inwithTaskCancellationHandlerand bridges the continuation through a small resume-once box (XPCReplyBox). On cancellation the continuation is resumed promptly; a late XPC reply becomes a no-op. The box guarantees the continuation is resumed exactly once. The connection is left intact on timeout; only the pending request is abandoned.Testing
Verified by static / code-level review; not built locally (no macOS 26 toolchain available here) — CI build will validate.