-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[concurrency] Allow calls to sync actor functions to be interpreted as async #34678
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
Conversation
9785602
to
469785e
Compare
Right now, this PR doesn't include the appropriate expansion of the AST into SIL. I can add that part onto this PR if it's looking good so far in terms of type checking. @swift-ci please smoke test |
@swift-ci please smoke test |
Small side question on API/ABI evolution I wanted to sanity check here:
On one hand... it definitely seems safe from outside perspective: they only could ever call it as async, so if I make it "really async now" for outside callers nothing changes. (Is this an ABI safe refactoring then, I would hope so?) But... if someone makes an It's kind of weird because "inside actor" even though the async function is a suspension point, it will never really suspend at that call it may suspend inside that function that is now async... The concern here is:
I guess I'm just realizing that this makes it easy to get into a specific case of source breaking... (only "inside the actor") but still; I guess we should call this tricky bit out when we document this automatic ability? |
Thanks for asking about this @ktoso!
It's probably confusing from my naming scheme of "implicitly async" in the code and also the title of this PR, but synchronous actor functions remain synchronous, i.e., their calling convention does not change. So, no new ABI/API issues should arise as a result of these changes. At a high-level, what this PR does is push the responsibility of creating an async version of a sync function into the compiler, instead of onto the programmer. For example, consider this code: actor class A {
func foo() { /*...*/ }
func bar() { foo() }
}
func f() async {
let a = A()
a.foo() // currently, illegal!
} We can't invoke the synchronous actor class A2 {
func foo() { /*...*/ }
func bar() { foo() }
func fooAsync() async { foo() } // New entry-point added by the programmer.
} Then the programmer would simply write
So, actor class A {
func foo() { /*...*/ }
func bar() { foo() } // this remains unchanged, no await!
}
func f() async {
let a = A()
await a.foo() // with this PR, this is now OK!
} Now, we could do better when actually implementing this interpretation a call-site in the compiler to look more like |
Thanks Kavon, yeah that's all fine; From the outside it's all fine I believe -- I was pondering the compatibility story for the following (why I mention extensions up there): public actor class A {
public func foo() { /*...*/ }
}
extension A { // someone does this in their code
func hello() { foo() } // no await needed
}
func f() async {
let a = A()
await a.foo() // with this PR, this is now OK!
} which needs to actor class A {
func foo() async { if await Task.currentDeadline() ... }
}
extension A { // someone does this
func hello() { await foo() } // change required; someone who had such extension, must now await
}
func f() async {
let a = A()
await a.foo() // outside of actor no changes, yay!
} I was worrying about "what if people put extensions on my actor and use But that's the same with any public API I suppose... Just means that this PR does not solve this and that's fine and expected. For the "outside" of my actor ABI/API it is compatible to make foo() async, because they always had to invoke it as async to begin with? |
ab9b938
to
5fba588
Compare
Ahh, I see what you mean. So, this PR adds some forwards compatibility for clients when an API changes between sync & async, but not in all cases. Also, the forwards compatibility is only at the source level, not at all in the ABI level: we cannot link the previously compiled client code with the newly changed library code at all... it requires a recompile. While I view this minor forwards compatibility as an accidental feature, perhaps if we're not clear that sync/async changes are still breaking, API designers might accidentally break clients they forgot about client-side extensions of the actor class. |
5fba588
to
9d8a9d5
Compare
@swift-ci please smoke test |
32c493c
to
03bb5c7
Compare
@swift-ci please smoke test |
03bb5c7
to
f0a3bcf
Compare
f0a3bcf
to
f2ce3ff
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking really good!
a251577
to
90e62d9
Compare
@swift-ci please smoke test and merge |
Thanks for clarifying the ABI bit, it's a bit surprising to be honest... So if I have an actor which needs to start checking for cancellation and I forgot to make the function async because I mostly looked at it from the outside I'd be in trouble and need a new function... 🤔 You folks know better if that's a real ABI concern or not really I guess. One can catch this in review I guess to "future proof" APIs hm. Just raising the concern about those "forgot that I didn't declare it async", otherwise the feature is cool 👍 |
Specifically, actor instance methods and global actor funcs. We also prevent partially-applied actor methods from being considered async.
90e62d9
to
65c175b
Compare
65c175b
to
3e613a2
Compare
@swift-ci please smoke test and merge |
In the current proposal for Actors, we have the following restrictions:
"[...] synchronous instance methods of actor classes are actor-isolated and, therefore, not available from outside the actor instance."
"[...] synchronous functions may only be invoked by the specific actor instance itself, and not even by any other instance of the same actor class."
Some discussion about this proposal [1] led to the suggestion that loosens these restrictions:
Synchronous methods of an actor class are available to code outside of an actor instance (i.e., can be accessed outside of
self
). But, such synchronous calls will be automatically treated as an async call, requiring the use ofawait
, etc.There is tentative support for this loosened restriction right now. This PR implements the suggestion in the type checker.
[1] https://forums.swift.org/t/concurrency-actors-actor-isolation/41613/75
Resolves rdar://71253835
TODO:
let
.async let