TaskGroup Crash

I’m seeing a crash that looks to be related to TaskGroup from Swift Concurrency. I can’t catch it in the debugger, only when running on device. It looks like the crash is actually in the Swift Concurrency library, and I’m not sure how to trace from there back to something in my own code...

Some digging so far suggests the issue only happens:

  • on iOS 15.0->15.3.x (ie. it looks to not happen on 15.4)
  • on older iPhones such as 6s, 6s plus, 7, 8, X

Where do I start trying to figure out the root cause of this?

Replies

So if I'm ready this correctly the TooltipView view's observableObject Model is placing a call to a generic Store find method using a dynamic key path lookup. What's going on in Store.find<A>(:) ?

  • That’s right. My 'store' uses reflection (Mirror.descendant()) to find a given model instance inside its storage. The Store.find<M:Model>(type:M) -> M? performs the find.

Add a Comment

Here’s the only code in my codebase that uses TaskGroup.

// Function that processes a bunch of 'action triggers' and produces a stream of actions
func run(action: Action, find: @escaping (Model.Type) -> Model?) -> AsyncStream<Action> {
    AsyncStream { continuation in
        Task {
            await withTaskGroup(of: Void.self) { group in
                for trigger in triggers {
                    group.addTask {
                        for await result in trigger(action, find) {
                            if result is VoidAction { continue }
                            continuation.yield(result)
                        }
                    }
                }

                await group.waitForAll()
                continuation.finish()
            }
        }
    }
}

// Later, from an async context. Process an action and dispatch its output.
Task {
    for await output in run(action: action, find: { store.find($0) }) {
        try await store.dispatch(output)
    }
}

I was able to solve the crash by explicitly marking both the task group’s closures as @MainActor. But would love to get a deeper understanding of why this doesn’t work.

            await withTaskGroup(of: Void.self) { @MainActor group in
                for trigger in triggers {
                    group.addTask { @MainActor in
                        for await result in trigger(action, find) {
                            if result is VoidAction { continue }
                            continuation.yield(result)
                        }
                    }
                }

                await group.waitForAll()
                continuation.finish()
            }

Just means something meant to only work on the main thread was being called on the background since it no longer crashes after bringing the task group back onto the main thread.