Skip to content

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

Description

@lakshitsoni26

I have done the following

  • I have searched the existing issues
  • 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

- OS: macOS 26.3.1 (25D2128)
- Xcode:  26.6 (17F113)
- Container: container CLI version 1.1.0

Code of Conduct

  • I agree to follow this project's Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions