[Bug]: XPCClient causes fatal SWIFT TASK CONTINUATION MISUSE crash on delayed replies
- Dominant language
- Swift
- Stars
- 49.9k
- Forks
- 1.8k
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 22
Description
### I have done the following
- [x] I have searched the existing issues
- [x] If possible, I've reproduced the issue using the 'main' branch of this project
### Steps to reproduce
This race condition can be reliably reproduced by injecting an artificial delay into the C-level libxpc callback while calling XPCClient.send().
1. Check out the main branch.
2. In any XPC test file (e.g., Tests/ContainerXPCTests/XPCClientTests.swift), add a test that configures the mock XPC connection to delay its reply for 3 seconds:
func testXPCClientTimeoutRaceCondition() async throws {
// 1. Setup mock connection to sleep for 3.0s before replying
let client = XPCClient(connection: slowMockConnection)
do {
// 2. Call with a shorter timeout (1.0s)
_ = try await client.send(message, timeout: 1.0)
} catch {
print("Task successfully timed out")
}
// 3. Keep the process alive to wait for the delayed C callback to finally fire
try await Task.sleep(nanoseconds: 3_000_000_000)
// -> FATAL CRASH occurs here: SWIFT TASK CONTINUATION MISUSE
}
3. Run the test: swift test --filter testXPCClientTimeoutRaceCondition
4. Observe that the Swift task correctly times out after 1 second, but 2 seconds later the libxpc callback fires and crashes the entire process.
### Problem description
When XPCClient.send() races a Swift timeout task against a libxpc callback, the Swift timeout can cancel the task group. However, because C callbacks cannot be cancelled natively in Swift Concurrency, the delayed XPC callback will eventually fire after the timeout task has completed. It then attempts to resume a CheckedContinuation that was already torn down, causing a fatal SWIFT TASK CONTINUATION MISUSE crash (resuming a continuation more than once).
The correct behavior should be to wrap the CheckedContinuation in a private thread-safe Actor (like ContinuationResolver) so that the continuation can only be resumed exactly once, and late XPC replies are safely swallowed as no-ops instead of crashing the daemon.
### Environment
```markdown
- OS: macOS 26.3.1 (25D2128)
- Xcode: 26.6 (17F113)
- Container: container CLI version 1.1.0
```
### Code of Conduct
- [x] I agree to follow this project's Code of Conduct
Contributor guide
Research direction
Start in XPCClient.send() and inspect the continuation and timeout handling, then read Tests/ContainerXPCTests/XPCClientTests.swift. Run swift test --filter testXPCClientTimeoutRaceCondition with the delayed mock reply. Done means the call times out after one second, the delayed callback is handled safely, and the process does not crash.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- swift
- Domain
- operating-systems
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100