Suggested tips for `swift_Concurrency_fatalError` crashes?

Hello all,

I'd appreciate any tips on what to look out for / debugging the crash below.

  • Crashes are from an iOS 18.5 device.
  • Only occurs on release style builds built with Xcode 16.4.

Thanks!

SIGABRT ABORT 0x00000001edd1e1dc

Crashed: com.apple.root.user-initiated-qos.cooperative
0  libsystem_kernel.dylib         0xb1dc __pthread_kill + 8
1  libsystem_pthread.dylib        0x7c60 pthread_kill + 268
2  libsystem_c.dylib              0x772d0 abort + 124
3  libswift_Concurrency.dylib     0x61e8c swift::swift_Concurrency_fatalError(unsigned int, char const*, ...) + 30
4  libswift_Concurrency.dylib     0x61eac setupStandardConcurrencyDescriptors() + 30
5  libswift_Concurrency.dylib     0x66250 swift_task_dealloc + 132
6  libswift_Concurrency.dylib     0x61424 asyncLet_finish_after_task_completion(swift::AsyncContext*, swift::AsyncLet*, void (swift::AsyncContext* swift_async_context) swiftasynccall*, swift::AsyncContext*, void*) + 88
7  libswift_Concurrency.dylib     0x5c134 swift::runJobInEstablishedExecutorContext(swift::Job*) + 292
8  libswift_Concurrency.dylib     0x5d5c8 swift_job_runImpl(swift::Job*, swift::SerialExecutorRef) + 156
9  libdispatch.dylib              0x13db0 _dispatch_root_queue_drain + 364
10 libdispatch.dylib              0x1454c _dispatch_worker_thread2 + 156
11 libsystem_pthread.dylib        0x9d0 _pthread_wqthread + 232
12 libsystem_pthread.dylib        0xaac start_wqthread + 8

It would be helpful if you could provide more information about the code. A reproducer would be best.

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

Thanks for the reproducer, please file it as an issue on GitHub - swiftlang/swift: The Swift Programming Language so we can track it

@kiel there are a few outstanding issue reports that look somewhat similar to your issue:

  1. Runtime crash: 'freed pointer was not the last allocation' · Issue #75501 · swiftlang/swift · GitHub
  2. Crash in Swift 6.1 when using async let inside a do {} block (Xcode 16.3+) · Issue #81771 · swiftlang/swift · GitHub

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.

1 Like