swiftlang / swift

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

Concurrency region based isolation does not understand `async let` #75121

Open pbk20191 opened 2 months ago

pbk20191 commented 2 months ago

Description

It seems like async let is not working well with isolation checking.

I'm trying to make backport feature of DiscardableTaskGroup. Thanks to generalized async-sequence evolution with backport of TaskGroup isolation. I was able to constrain all the mutation of TaskGroup to happen in the same isolation while, enqueueing multiple task to mutate TaskGroup

Reproduction

extension Actor {

    func withDiscardingGroup<TaskResult>(
        body:  (isolated Self, inout TaskGroup<Void>) async -> sending TaskResult
    ) async -> sending TaskResult {
        let holder: SafetyRegion = self as? SafetyRegion ?? .init()
        if await holder.isFinished {
            preconditionFailure("SafetyRegion is already used!")
        }
        return await withTaskGroup(of: Void.self, returning: TaskResult.self) { group in
            group.addTask(priority: .background) {
                /// keep at least one child task alive
                /// so that subTask won't return
                await holder.hold()
            }
            /// drain all the finished or failed Task
            async let subTask:Void = {
                // Error: 'self'-isolated 'group' is captured by a actor-isolated closure. actor-isolated uses in closure may race against later nonisolated uses
                while let _ =  await group.next(isolation: self) {
                    if await holder.isFinished {
                        break
                    }
                }
            }()
            // passing isolated self to extend isolation
            // Error:'self'-isolated 'body' is captured by a actor-isolated closure. actor-isolated uses in closure may race against later nonisolated uses
            let value = await body(self, &group)
            await subTask

            return value
        }
    }

}

@usableFromInline
package actor SafetyRegion {

    @usableFromInline
    internal(set) package var isFinished = false
    @usableFromInline
    internal var continuation: UnsafeContinuation<Void,Never>? = nil

    @inlinable
    package init() {

    }

    @usableFromInline
    package func markDone() {
//        guard !isFinished else { return }
        isFinished = true
        continuation?.resume()
        continuation = nil
    }

    @usableFromInline
    internal func hold() async {
        return await withUnsafeContinuation {
            if isFinished {
                $0.resume()
            } else {
                if let old = self.continuation {
                    assertionFailure("received suspend more than once!")
                    old.resume()
                }
                self.continuation = $0
            }
        }
    }

}

compiler is saying additonal message: Pattern that the region based isolation checker does not understand how to check. Please file a bug

Expected behavior

since everything is in the same isolation, and I'm just extending the isolation.

there is three un-sendable data. closure, taskGroup, and closure return. In this case it is pretty obvious that every thing is captured by the same actor.

Environment

xcode 16.03 beta toolchain m1 macbook air

Additional information

For now, using unchecked Sendable and nonisolated annotation can bypass compiler check as an workaround.

Will this fix on next release, because of this?

hborla commented 2 months ago

@gottesmm