TaskGroup-Related Crash

I’m seeing a crash relating to TaskGroup, but unfortunately it’s only coming up in production and I have limited information on it (via Crashlytics). I’m guessing I’m abusing TaskGroup one way or another. Any advice as to where to start investigating an issue like this? Here’s the offending thread. I’ve tried at length to reproduce it locally but no luck.

Crashed: com.apple.root.user-initiated-qos.cooperative
EXC_BAD_ACCESS KERN_INVALID_ADDRESS 0x0000000000000040

Crashed: com.apple.root.user-initiated-qos.cooperative
0  libswift_Concurrency.dylib     0x31440 swift::TaskGroup::offer(swift::AsyncTask*, swift::AsyncContext*) + 504
1  libswift_Concurrency.dylib     0x2dc5c swift::AsyncTask::completeFuture(swift::AsyncContext*) + 132
2  libswift_Concurrency.dylib     0x2f4d0 completeTaskAndRelease(swift::AsyncContext*, swift::SwiftError*) + 128
3  libswift_Concurrency.dylib     0x2b0f0 swift::runJobInEstablishedExecutorContext(swift::Job*) + 132
4  libswift_Concurrency.dylib     0x2b9f8 swift_job_runImpl(swift::Job*, swift::ExecutorRef) + 72
5  libdispatch.dylib              0x1343c _dispatch_root_queue_drain + 340
6  libdispatch.dylib              0x13c38 _dispatch_worker_thread2 + 172
7  libsystem_pthread.dylib        0x4e48 _pthread_wqthread + 224
8  libsystem_pthread.dylib        0x49f0 start_wqthread + 8

I have only one use of TaskGroup in my project, and it seems pretty trivial.

// 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)
    }
}

Is there any way to dig into this without a more complete crash report?

1 Like

Thanks for sharing your code. Hopefully someone more knowledgable than myself can reply. I was hitting a similar (but not the same) problem where my store.dispatch contained some Core Data related code that was not following proper managed object thread containment rules. (It mostly worked, except for sometimes. :grimacing: :sweat_smile:)

A random thought I had. Is it okay for the different async child tasks to use the continuation variable concurrently? IOW, is continuation.yield re-enterant?

On which iOS version? I met similar crashes.
But after upgrading my device to latest iOS version,
the bug seems to be fixed.

I eventually narrowed this down to iOS 15.3, and older (iPhone 6s, 7, 8, X) devices. It seems to be fixed on iOS 15.4. I also fixed it by marking the two inner closures (withTaskGroup, addTask) @MainActor.

I've also seen this crash. I can confirm it is reproducible on versions prior to 15.4 which is a blocker for adopting structured concurrency at my company.
I had hope Swift 5.6 would fix this issue. It seemingly did but a library responsible for the crash was for some reason not updated in Xcode 13.3 toolchain.

2 Likes

I got this failure on iOS 15.5 - could someone help check if it's the same error?

Crashed: com.apple.root.user-initiated-qos.cooperative
0 libswiftCore.dylib 0x39b74 assertionFailure(:_:file:line:flags:) + 308
1 libswift_Concurrency.dylib 0x5ed8 CheckedContinuation.resume(returning:) + 560
2 [AppName] 0x1fe0dc (1) await resume partial function for closure #2 in closure #2 in closure #1 in closure #1 in MsgRepo.fetchMsgs(storyId:updatedAfter:) + 4341768412 (:4341768412)
3 libswift_Concurrency.dylib 0x3b7cc swift::runJobInEstablishedExecutorContext(swift::Job*) + 244
4 libswift_Concurrency.dylib 0x3c1e8 swift_job_runImpl(swift::Job*, swift::ExecutorRef) + 72
5 libdispatch.dylib 0x15164 _dispatch_root_queue_drain + 396
6 libdispatch.dylib 0x1596c _dispatch_worker_thread2 + 164
7 libsystem_pthread.dylib 0x1080 _pthread_wqthread + 228
8 libsystem_pthread.dylib 0xe5c start_wqthread + 8