swiftlang / swift

The Swift Programming Language
https://swift.org
Apache License 2.0
67.58k stars 10.36k forks source link

Can't enqueue main-actor-isolated async func directly with addTask #76488

Open jumhyn-browser opened 1 month ago

jumhyn-browser commented 1 month ago

Description

In the below example, the call to addTask which calls h give a concurrency warning that seems incorrect. The given error is:

error: sending value of non-Sendable type '() async -> ()' risks causing data races
note: Passing main actor-isolated value of non-Sendable type '() async -> ()' as a 'sending' parameter risks causing races inbetween main actor-isolated uses and uses reachable from the call

Am I missing something about why this is not allowable in the general case?

Reproduced on godbolt nightly but have seen the same/similar issue adopting Swift 6.0

Reproduction

@MainActor
func f() async {
    await withTaskGroup(of: Void.self) { group in
        for x in 0..<10 {
            group.addTask { @MainActor in
                g(x)
            }

            group.addTask { @MainActor in // error
                await h(x)
            }
        }
    }
}

@MainActor
func g(_: Int) {}

@MainActor
func h(_: Int) async {}

Expected behavior

Both calls to addTask compile without warning.

Environment

Swift version 6.1-dev (LLVM b5c60054ab3ee7b, Swift 09b543187803093)
Target: x86_64-unknown-linux-gnu
Compiler returned: 0

Additional information

No response

jamieQ commented 1 month ago

here's a reduction for future triaging/investigations:

func f() async {
    await withTaskGroup(of: Void.self) { group in
        group.addTask { @MainActor in
            await Task.yield()
        }
    }
}
/*
<source>:3:15: error: sending value of non-Sendable type '() async -> ()' risks causing data races
 1 | func f() async {
 2 |     await withTaskGroup(of: Void.self) { group in
 3 |         group.addTask { @MainActor in
   |               |- error: sending value of non-Sendable type '() async -> ()' risks causing data races
   |               `- note: Passing main actor-isolated value of non-Sendable type '() async -> ()' as a 'sending' parameter risks causing races inbetween main actor-isolated uses and uses reachable from the callee
 4 |             await Task.yield()
 5 |         }
*/

it also appears it's not unique to the main actor – any global actor exhibits the issue. the following approaches all seem to avoid the diagnostic:

func f() async {
    await withTaskGroup(of: Void.self) { group in
        // 1
        let op: @MainActor () async -> Void = { await Task.yield() }
        group.addTask(operation: op)

        // 2
        group.addTask(operation: { await Task.yield() } as @MainActor () async -> Void)

        // 3
        group.addTask { @Sendable @MainActor in
            await Task.yield()
        }
    }
}

edit: this is perhaps the same underlying issue as https://github.com/swiftlang/swift/issues/76242