Skip to content

[6.0][sending] Add support for Task.init and friends and TaskGroup and friends to take sending closures #74608

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jun 21, 2024

Conversation

gottesmm
Copy link
Contributor

@gottesmm gottesmm commented Jun 21, 2024

Explanation: This PR marks Task.init, Task.detached/et al and all the TaskGroup APIs as taking their operation as a sending non-Sendable parameter instead of as a @sendable parameter. This allows for users to capture non-Sendable values into the closures used to call these APIs. E.x. we can write the following example which we couldn't before:

func example() {
  let x = NonSendableKlass()
  Task {
      useX(x)
   }
}

The rest of the changes in this PR were needed to work around other issues in the compiler that prevented us from doing the above.

Radars:

  • rdar://130171939

Original PRs:

Risk: Low.
Testing: Added tests to the compiler.
Reviewer: N/A

@gottesmm gottesmm requested a review from a team as a code owner June 21, 2024 04:39
@gottesmm
Copy link
Contributor Author

@swift-ci test

@gottesmm
Copy link
Contributor Author

Original: #74239

@gottesmm
Copy link
Contributor Author

@swift-ci test

gottesmm added 12 commits June 21, 2024 06:58
…uming parameter

The reason why I am doing this is that:

1. We don't support no escaping closure parameters today and would like
   to leave the design space open.

2. These closures are bitwise copyable/trivial so marking them as
   specifically consuming doesn't really make sense.

(cherry picked from commit 4d7af7b)
…now inferred to be nonisolated.

Consider the following piece of code and what the isolation is of the closure
literal passed to doSomething():

```swift
func doSomething(_ f: sending () -> ()) { ... }

@MyCustomActor
func foo() async {
  doSomething {
    // What is the isolation here?
  }
}
```

In this case, the isolation of the closure is @MyCustomActor. This is because
non-Sendable closures are by default isolated to their current context (in this
case @MyCustomActor since foo is @MyCustomActor isolated). This is a problem
since

1. Our closure is a synchronous function that does not have the ability to hop
to MyCustomActor to run said code. This could result in a concurrency hole
caused by running the closure in doSomething() without hopping to
MyCustomActor's executor.

2. In Region Based Isolation, a closure that is actor isolated cannot be sent,
so we would immediately hit a region isolation error.

To fix this issue, by default, if a closure literal is passed as a sending
parameter, we make its isolation nonisolated. This ensures that it is
disconnected and can be transferred safely.

In the case of an async closure literal, we follow the same semantics, but we
add an additional wrinkle: we keep support of inheritActorIsolation. If one
marks an async closure literal with inheritActorIsolation, we allow for it to be
passed as a sendable parameter since it is actually Sendable under the hood.

(cherry picked from commit 3f39bdc)
…endable function.

We view the conversion from a Sendable to a non-Sendable function via
convert function to produce a new fresh sendable value. We should
squelch that error.

(cherry picked from commit b786c60)
…properly mark actor isolated async closures passed to an inheritActorContext argument as being Sendable.

The reason why I am fixing this is that otherwise, we get a warning when one
creates an actor isolated closure and pass it into a task, e.x.:

```swift
@mainactor func test() {
  // We would get a warning on the closure below saying that we are sending
  // a closure that is MainActor isolated.
  Task {
    ...
  }
}
```

(cherry picked from commit f781ad3)
…tead of __owned @sendable so we can capture non-isolated values.

(cherry picked from commit 4697546)
… obsolete in swift 6.

These APIs remained to preserve source compatibility during bringup. We do not
want them used in swift 6 mode... so deprecate in swift 5 and obsolete in swift
6.

(cherry picked from commit b541e02)
…oSendingParameter.

This came up while I was talking with @xedin. This name makes
it really clear what we are trying to communicate to the author.

(cherry picked from commit 5e27999)
There is only one call site of this helper function, so we are not gaining any
flexibility by doing this. It only makes it harder to reason about what the
function actually does without providing any benefit.

I am doing this before I make in the next commit an actual semantic changing
commit to ease review.

(cherry picked from commit ca6cde6)
…g DeclContexts.

Without this, we were hitting weird behaviors in flow_isolation.swift only when
compiling in Swift 5 mode.

Consider the following example:

```
func fakeTask(@_inheritActorContext _ x: sending @escaping () async -> ()) {}

actor MyActor {
    var x: Int = 5
    var y: Int = 6
    var hax: MyActor? = nil

    init() {
        fakeTask {
            // Warning! You can remove await
            // Error! Cannot access actor state in nonisolated closure.
            _ = await self.hax
        }
    }
}
```

The reason why this was happening is that we were not understanding that the
closure passed to fakeTask is not part of the init and has different isolation
from the init (it is nonisolated but does not have access to the init's state).
This then caused us to exit early and not properly process the isolation
crossing point there.

Now, we properly understand that a sending inheritActorContext closure (just
like an @sendable closure) has this property in actor initializers.

(cherry picked from commit d54e6ba)
…ding non-Sendable function instead of an @sendable function.

This matches the interface of the public stdlib APIs that wrap these builtin calls.

(cherry picked from commit 43e1c54)
(cherry picked from commit d28eef2)
(cherry picked from commit 3685cdd)
@gottesmm
Copy link
Contributor Author

@swift-ci test

@gottesmm gottesmm enabled auto-merge June 21, 2024 17:42
Copy link
Contributor

@xedin xedin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sema bits look reasonable to me.

@gottesmm gottesmm merged commit 4d6af20 into swiftlang:release/6.0 Jun 21, 2024
3 checks passed
@gottesmm gottesmm deleted the release/6.0-task branch June 21, 2024 21:49
Copy link
Contributor

@ktoso ktoso left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Just double checked the APIs where we applied this.

Thanks Michael :)

@@ -255,6 +255,8 @@ extension Task where Failure == Error {
@_spi(MainActorUtilities)
@MainActor
@available(SwiftStdlib 5.9, *)
@available(*, deprecated, message: "Use Task.init with a main actor isolated closure instead")
@available(swift, obsoleted: 6.0, message: "Use Task.init with a main actor isolated closure instead")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants