apple / apple/container

[Bug]: XPCClient causes fatal SWIFT TASK CONTINUATION MISUSE crash on delayed replies

Open
#2,007 0 comments 0 reactions 0 assignees View on GitHub
stability
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.