Skip to content

[sending] Mark Task.init, Task.detached and friends as taking a sending closure instead of a __owned @Sendable #74239

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 9, 2024

Just creating the PR now for discussion. Going to add more here.

Resolves: #74382

@gottesmm
Copy link
Contributor Author

@swift-ci smoke test

@gottesmm
Copy link
Contributor Author

@swift-ci smoke test

gottesmm added 11 commits June 21, 2024 02:24
…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.
…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.
…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.
…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 {
    ...
  }
}
```
…tead of __owned @sendable so we can capture non-isolated values.
… 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.
…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.
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.
…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.
…ding non-Sendable function instead of an @sendable function.

This matches the interface of the public stdlib APIs that wrap these builtin calls.
@gottesmm
Copy link
Contributor Author

@swift-ci smoke test

@gottesmm gottesmm merged commit 74944a6 into swiftlang:main Jun 21, 2024
3 checks passed
@gottesmm gottesmm deleted the taskinit-sending branch June 21, 2024 17:36
@groue
Copy link

groue commented Jul 24, 2024

Hello @gottesmm, I'm pretty happy that this pull request has shipped in Xcode 16 beta 4. 🙏

I was expecting that DispatchQueue.async(execute:) would also be amended to use sending instead of requiring a @Sendable closure. Is it worth reporting somewhere, or do you think it will just ship in a future beta?

@hborla
Copy link
Member

hborla commented Jul 24, 2024

I was expecting that DispatchQueue.async(execute:) would also be amended to use sending instead of requiring a @sendable closure.

Michael's PR here is part of the implementation for SE-0430, which only includes adoption of sending in the Concurrency library. Changes to Dispatch are not subject to the Swift Evolution process.

That said:

Is it worth reporting somewhere

It's a known issue.

harlanhaskins pushed a commit to harlanhaskins/swift that referenced this pull request Jul 29, 2024
This was deprecated in swiftlang#74239 with a message to replace with

```swift
Task { @mainactor in ... }
```

But that doesn't have equivalent semantics. `startOnMainActor` executes
the closure synchronously up to the first suspension point, which is
necessary for working with APIs like XPC connections and UIKit drag-and-drop.
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.

Swift 6: incorrect compiler error when passing a sending closure from a global-actor-isolated function
3 participants