Async XCTContext.runActivity?

I'm trying to create an async version of XCTContext.runActivity(). The challenge is how to make sure the async callback completes before returning to runActivity when it's all @MainActor.

At the moment my function looks like this:

extension XCTContext {
  @MainActor
  static func runAsyncActivity<Result: Sendable>(named name: String,
                                                 block: @MainActor @escaping (any XCTActivity) async throws -> Result) async throws -> Result {
    try await withCheckedThrowingContinuation { continuation in
      runActivity(named: name) { activity in
        let semaphore = DispatchSemaphore(value: 0) // Ensure runActivity waits for the task to complete
        Task {
          do {
            let result = try await block(activity)
            continuation.resume(returning: result)
          } catch {
            continuation.resume(throwing: error)
          }
          semaphore.signal()
        }
        semaphore.wait()
      }
    }
  }
}

This of course creates deadlock because semaphore.wait() suspends the thread so that the @MainActor child task can never run. If I use a detached task, then it will run outside the context created by runActivity which defeats the purpose. I tried using XCTWaiter to wait on an expectation instead of the semaphore and got the same result.

Is there a way to synchronously wait on a child task without deadlocking MainActor? Or is it not really doable, and this is why Apple didn't provide such a function in the first place?

XCTContext.runActivity() is implemented in Objective-C as a simple stack bound to the main thread, which is why this is just not going to work: it has no way to express the relative ordering of events when different tasks could hop on the main actor, start an activity, then suspend.

Apple is tracking this issue, but it's beyond the scope of what we're able to discuss here as XCTest[1] is not part of the Swift open source project.


  1. Inside you, there are two wolves XCTests: XCTest.framework, which is proprietary and ships with Xcode for Apple platforms; and swift-corelibs-xctest, which is open source, mimics XCTest.framework, and does not include XCTContext. ↩ī¸Ž

2 Likes