Description
Description
In Swift 6.1 (Xcode 16.3 and 16.4), placing async let
bindings inside a do block causes a runtime crash—even when those values are properly awaited and the code is enclosed in do-catch. The crash does not occur in Xcode 16.2 or earlier.
This behavior seems to be related to how task deallocation is handled for async let
when declared inside lexical scopes.
Thanks to @IrixVol for sharing the reproducible project: LetAsyncCrash
Reproduction
func crashingAsyncLetUsage() async {
do {
async let value1 = fetchValue1()
async let value2 = fetchValue2()
let result1 = try await value1()
let result2 = try await value2()
let model = buildModel(result1, result2)
await consume(model)
} catch {
print("Caught error: \(error)")
}
}
func safeAsyncLetUsage() async {
async let value1 = fetchValue1()
async let value2 = fetchValue2()
do {
let result1 = try await value1()
let result2 = try await value2()
let model = buildModel(result1, result2)
await consume(model)
} catch {
print("Caught error: \(error)")
}
}
Stack dump
Thread 12 Crashed:
0 libsystem_kernel.dylib 0x102628874 __pthread_kill + 8
1 libsystem_pthread.dylib 0x1015be2ec pthread_kill + 264
2 libsystem_c.dylib 0x180170568 __abort + 112
3 libsystem_c.dylib 0x1801704f8 abort + 116
4 libswift_Concurrency.dylib 0x2493a346c swift::swift_Concurrency_fatalErrorv(unsigned int, char const*, char*) + 28
5 libswift_Concurrency.dylib 0x2493a3488 swift::swift_Concurrency_fatalError(unsigned int, char const*, ...) + 28
6 libswift_Concurrency.dylib 0x2493a6718 swift_task_dealloc + 124
7 libswift_Concurrency.dylib 0x2493a27a4 asyncLet_finish_after_task_completion(swift::AsyncContext*, swift::AsyncLet*, void (swift::AsyncContext* swift_async_context) swiftasynccall*, swift::AsyncContext*, void*) + 72
8 Dev.debug.dylib 0x113f12529 implicit closure #6 in implicit closure #5 in implicit closure #4 in FooViewModel.sendAction<A>(_:) + 1
9 Dev.debug.dylib 0x113f1cddd partial apply for implicit closure #6 in implicit closure #5 in implicit closure #4 in FooViewModel.sendAction<A>(_:) + 1
10 Dev.debug.dylib 0x113ec8d6d thunk for @escaping @callee_guaranteed @async () -> () + 1
11 Dev.debug.dylib 0x113f1ccf9 partial apply for thunk for @escaping @callee_guaranteed @async () -> () + 1
12 Dev.debug.dylib 0x113f0f959 FooViewModel.sendAction<A>(_:) + 1 (FooViewModel.swift:)
13 Dev.debug.dylib 0x113f1b5c1 protocol witness for ActionCaster.sendAction<A>(_:) in conformance FooViewModel + 1
14 Dev.debug.dylib 0x113f0b10d closure #1 in FooViewModel.viewWillAppear(_:) + 1 (FooViewModel.swift:)
15 Dev.debug.dylib 0x113f0b2c5 partial apply for closure #1 in FooViewController.viewWillAppear(_:) + 1
16 Dev.debug.dylib 0x113e74cbd thunk for @escaping @isolated(any) @callee_guaranteed @async () -> (@out A) + 1
17 Dev.debug.dylib 0x113e74e29 partial apply for thunk for @escaping @isolated(any) @callee_guaranteed @async () -> (@out A) + 1
18 libswift_Concurrency.dylib 0x2493a5829 completeTaskWithClosure(swift::AsyncContext*, swift::SwiftError*) + 1
Expected behavior
async let
declared inside a do block should behave the same as when declared outside it, as long as all values are awaited. It should not result in a crash.
Environment
swift-driver version: 1.120.5 Apple Swift version 6.1.2 (swiftlang-6.1.2.1.2 clang-1700.0.13.5)
Target: arm64-apple-macosx15.0
Additional information
This may be a regression introduced by recent changes to task deallocation logic in Swift 6.1. The crash appears related to how async let scopes are finalized when declared in lexical blocks like do {}.