Modifying non-sendable class in Task with Swift Approachable Concurrency and Swift 6

Just want to add that even between your b(1) and b(2) there's a race.

Theoretically speaking, the caller and callee of b() are in different isolation domains, (the caller is @MainActor, the callee is @concurrent nonisolated), so the arguments passed to b, including local which corresponds to the implicit self, must be either Sendable or in a disconnected region. Neither is true for local, so the code should have been rejected.

You can easily verify the existence of this race by extending the operation a little bit longer and turning on TSAN:

public class A {
    var a = 0

    @concurrent
    func b(_ input: Int) async {
        for _ in 0..<100 {    // <- my modificatons
            a = input
        }
    }
}

@MainActor func doSomething() async {
    let local = A()

    Task {
        await local.b(1)
    }
    Task {
        await local.b(2)
    }

    try? await Task.sleep(for: .seconds(1))
}

await doSomething()

Console output:

==================
WARNING: ThreadSanitizer: data race (pid=45865)
  Write of size 8 at 0x0001043038d0 by thread T1:
    #0 ConcurrentAsync.A.a.setter : Swift.Int <null> (ConcurrentAsync:arm64+0x1000022fc)
    #1 (1) suspend resume partial function for ConcurrentAsync.A.b(Swift.Int) async -> () <null> (ConcurrentAsync:arm64+0x100002674)
    #2 swift::runJobInEstablishedExecutorContext(swift::Job*) <null> (libswift_Concurrency.dylib:arm64e+0x5c450)

  Previous write of size 8 at 0x0001043038d0 by thread T2:
    #0 ConcurrentAsync.A.a.setter : Swift.Int <null> (ConcurrentAsync:arm64+0x1000022fc)
    #1 (1) suspend resume partial function for ConcurrentAsync.A.b(Swift.Int) async -> () <null> (ConcurrentAsync:arm64+0x100002674)
    #2 swift::runJobInEstablishedExecutorContext(swift::Job*) <null> (libswift_Concurrency.dylib:arm64e+0x5c450)

  Location is heap block of size 24 at 0x0001043038c0 allocated by main thread:
    #0 malloc <null> (libclang_rt.tsan_osx_dynamic.dylib:arm64e+0x60500)
    #1 _malloc_type_malloc_outlined <null> (libsystem_malloc.dylib:arm64e+0x1d9d4)
    #2 (1) suspend resume partial function for ConcurrentAsync.doSomething() async -> () <null> (ConcurrentAsync:arm64+0x100001780)
    #3 swift::runJobInEstablishedExecutorContext(swift::Job*) <null> (libswift_Concurrency.dylib:arm64e+0x5c450)
    #4 start <null> (dyld:arm64e+0xfffffffffff3ab94)

  Thread T1 (tid=161502921, running) is a GCD worker thread

  Thread T2 (tid=161502922, running) is a GCD worker thread
2 Likes