Task capture list silence data race error?

the issue is that if NonSendable.stuff() is allowed to run concurrently, it could introduce data races depending on what it does internally. for example, if we alter the code to something like this:

// run with `swiftc -swift-version 6 -sanitize=thread -Xfrontend -parse-as-library <file>.swift && ./<file>`
class NonSendable {
    var state = [Int]()

    func stuff() async {
        for i in 1...100 {
            state.append(i)
        }
    }
}

@MainActor
final class Foo {
    let value = NonSendable()

    func perform() async {
        let t1 = Task { [value] in
            await value.stuff()
        }

        let t2 = Task { [value] in
            await value.stuff()
        }

        _ = await (t1.value, t2.value)
    }
}

@main
enum E {
    static func main() async {
        await Foo().perform()
    }
}

since stuff() is nonisolated and async, the current language semantics mean it will run on the global concurrent executor. as such, there's a race on the internal mutable state, which will typically lead to the program crashing (and TSAN flagging it) at runtime.

the compile-time analysis is supposed to allow passing non-sendable values around somewhat more permissively if they are known to be in 'disconnected' regions. in this particular case though, the value property should presumably always be considered part of the main actor's region, since it is stored state on a @MainActor type.

as an aside – i do wonder if that means value.stuff() should effectively never be callable in this configuration, since i'm not sure how the system could rule out possible re-entrance leading to concurrent execution... but i might be missing something here.

1 Like