Thanks for the reply. I know it would have been helpful to provide code and I'm not sure I can distill it down, which is why I was wondering if there were any tips.
After a bit of elimination, the problematic code is something like the below. If I await these three serially, the code does not crash. I'm trying to figure out which combination is more crash prone. I use this async let pattern in other places around the codebase and they do not crash, either.
func getStuffFromNetwork(apiClient: APIClient) async throws -> Stuff
async let data1Async = apiClient.request(…)
async let data2Async = apiClient.request(…)
async let data3Async = apiClient.request(…)
let (data1, data2, data3) = try await (data1Async, data2Async, data3Async)
// process data to make stuff
return stuff
}
I have managed to catch this async let fatal error in a debug build.
As I step through my program, I can see the request completes as expected but on the very last line, just before a return statement/exiting the scope, the crash occurs.
I still cannot create a reproduction of it, but I can work around it by not using async let for certain calls.
Any suggestions for further information I can provide?
The Console reads:
freed pointer was not the last allocation
The stack trace reads:
Thread 11 Queue : com.apple.root.user-initiated-qos.cooperative (concurrent)
#0 0x00000001051a4874 in __pthread_kill ()
#1 0x0000000104f1e2ec in pthread_kill ()
#2 0x0000000180171ea8 in abort ()
#3 0x0000000249df5310 in swift::swift_Concurrency_fatalErrorv ()
#4 0x0000000249df532c in swift::swift_Concurrency_fatalError ()
#5 0x0000000249df8640 in swift_task_dealloc ()
#6 0x0000000249df4b64 in asyncLet_finish_after_task_completion ()
#7 0x0000000249df0988 in swift::runJobInEstablishedExecutorContext ()
#8 0x0000000249df1c28 in swift_job_runImpl ()
#9 0x0000000105113290 in _dispatch_root_queue_drain ()
Very roughly, the crash prone code looks something like:
struct ContentView: View {
var body: some View {
Text("Hi")
.task {
let thing = await getThing()
print(thing ?? "No value")
}
}
struct Thing {
var id: Int
}
func getThing() async -> Thing? {
let client = APIClient()
async let responseAsync = client.get(.specific(.full), responseType: ResponseObject.self)
async let anotherResponseAsync = client.get(.specific(.that), responseType: ResponseObject.self)
let (response, anotherResponse) = try! await (responseAsync, anotherResponseAsync)
var thing = Thing(id: 86)
thing.id = max(response.id, anotherResponse.id) // here is where the fatal error occurs
return thing
}
enum Endpoint {
enum SpecificEndpointOption {
case full
case this
case that
}
case specific(SpecificEndpointOption)
}
struct ResponseObject: Decodable {
var id: Int
}
struct APIClient {
func get<ResponseType: Decodable>(_ endpoint: Endpoint, responseType: ResponseType.Type, decoder: JSONDecoder = JSONDecoder()) async throws -> ResponseType {
try await Task {
try await Task.sleep(nanoseconds: NSEC_PER_SEC)
return try decoder.decode(ResponseType.self, from: "{\"id\": 0}".data(using: .utf8)!)
}.value
}
}
}
issue 2. has a reproducible example, but seems dependent on using a specific version of iOS as the minimum deployment target (iOS 15 specifically). given that, i'm curious what the minimum deployment target in your case is.