I'm hitting an occasional crash in some code which uses Swift's new concurrency features. This crash never seems to happen on development builds, either in the simulator or when I install the code on a device directly from Xcode. However it's happening pretty frequently when folks install the code from TestFlight.
The actual crash is this:
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000040
Exception Codes: 0x0000000000000001, 0x0000000000000040
Thread 6 name:
Thread 6 Crashed:
0 libswift_Concurrency.dylib 0x00000001e6df7440 swift::TaskGroup::offer(swift::AsyncTask*, swift::AsyncContext*) + 504 (TaskGroup.cpp:603)
1 libswift_Concurrency.dylib 0x00000001e6df3c5c swift::AsyncTask::completeFuture(swift::AsyncContext*) + 132 (Task.cpp:180)
2 libswift_Concurrency.dylib 0x00000001e6df54d0 completeTaskAndRelease(swift::AsyncContext*, swift::SwiftError*) + 128 (Task.cpp:323)
3 libswift_Concurrency.dylib 0x00000001e6df5425 completeTaskWithClosure(swift::AsyncContext*, swift::SwiftError*) + 1 (Task.cpp:365)
I'm not sure exactly what line(s) of code this corresponds with, but it's likely somewhere in here:
class MyClass {
var results: [String: Set<String>] = [:]
func getSingleResult(index: Int) async -> (String, Set<String>) {
// ....
}
func getAllResults(range: ClosedRange<Int>) async -> [String: Set<String>] {
await withTaskGroup(
of: (String, Set<String>).self,
returning: [String: Set<String>].self
) { [self] group in
for i in range {
group.addTask { await getSingleResult(index: i) }
}
var results: [String: Set<String>] = [:]
for await result in group {
results[result.0] = result.1
}
return results
}
}
func blockingDoWork(range: ClosedRange<Int>) {
results = [:]
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
Task.init {
results = await getAllResults(range: range)
dispatchGroup.leave()
}
dispatchGroup.wait()
// Now do something with `results`
}
I'm trying to bridge the divide between synchronous and asynchronous code (perhaps the wrong way). Basically I have a single thread/synchronous code which then creates a variable number of asynchronous calls and attempts to block until all those calls finish. It aggregates the results of those calls in the results class member, which perhaps isn't the best approach but was the only way I could get the asynchronous code to communicate with the synchronous.
This code seems to run fine in a development build, and runs 1000s of times in a release build but then crashes.
I can't seem to turn on the thread sanitizer or address sanitizer because my Xcode project uses Swift Package Manager, and there's a bug when using those two which causes the build to fail.
Any ideas what might be going wrong? I assume I'm getting lucky with the development builds and that I've got some fundamental problem in this code, but I don't know enough about the subtleties of Swift's new concurrency features to recognize it.