-
Notifications
You must be signed in to change notification settings - Fork 656
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
Changed NIOAsyncChannel.executeThenClose to accept actor-isolated par… #3121
base: main
Are you sure you want to change the base?
Conversation
@@ -283,8 +283,11 @@ public struct NIOAsyncChannel<Inbound: Sendable, Outbound: Sendable>: Sendable { | |||
/// | |||
/// - Important: After this method returned the underlying ``Channel`` will be closed. | |||
/// | |||
/// - Parameter body: A closure that gets scoped access to the inbound and outbound. | |||
/// - Parameter: | |||
/// - isolatedTo: actor where this function should be isolated to |
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.
Nit: the parameter name here is actor
- isolatedTo
is the label (you can think of it as a part of the method's name).
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.
Fixed the issue.
public func executeThenClose<Result>( | ||
isolatedTo actor: isolated(any Actor)? = #isolation, |
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 change is actually API-breaking: even if the parameter you add is defaulted, the function signature changes.
For instance, imagine you have:
func someFunc(foo: Int) { ... }
func doSomething() {
let f: (Int) -> Void = someFunc(foo:)
// ...
}
Say you add a new defaulted parameter...
func someFunc(foo: Int, bar: String = "baz") { ... }
func doSomething() {
let f1: (Int) -> Void = someFunc(foo:) // this will fail to build: "no member named someFunc(foo:)"
// Should be:
let f2: (Int, String?) -> Void = someFunc(foo:bar:)
}
You will have to add a new executeThenClose
overload that takes a (non-optional) isolated param.
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.
Great point. I've added the old API back and have implemented it using the new one.
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.
Thanks for this change, it will make it much easier to use asyncchannels!
Couple of points:
- this will not work on older compilers, so probably needs some #if compiler >= 6.0 sprinkled around
- following on from gus's comment, are we sure the overload resolution will pick the right thing here? Can we add a test which uses this from an actor to make sure that compiles fine?
The testcase would be something like having an actor-isolated function call executeThenClose and then attempt to reference self inside the closure
/// - actor: actor where this function should be isolated to | ||
/// - body: A closure that gets scoped access to the inbound and outbound. | ||
public func executeThenClose<Result>( | ||
isolatedTo actor: isolated (any Actor)?, |
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.
We seem to have dropped the defaulted parameter here which I think we want.
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.
The problem is the compiler picking the right func if we default this parameter (i.e. if you call executeThenClose { ... }
are we calling the old version, or the one defaulting isolation
?).
We could use @_disfavoredOverload
, but have we agreed that is the route we generally want to take?
Specifically in this case, if what we want is to enable adopters to use executeThenClose
from actors, I don't think it's particularly bad to have to explicitly pass self
as isolation
, as it makes it clear you want to run this on the actor, even though you're await
ing and would otherwise be hopping off the isolation domain.
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.
As spoken offline, we decided to favour the use of @_disfavoredOverload
with this API. I'll change the code.
Looks good in general, unfortunately there are some fiddly considerations here. Here's a link to a similar block of code which includes the compiler check which Hamzah mentions https://github.com/apple/swift-service-context/blob/8946c930cae601452149e45d31d8ddfac973c3c7/Sources/ServiceContextModule/ServiceContext.swift#L232-L266 |
@@ -284,6 +284,7 @@ public struct NIOAsyncChannel<Inbound: Sendable, Outbound: Sendable>: Sendable { | |||
/// - Important: After this method returned the underlying ``Channel`` will be closed. | |||
/// | |||
/// - Parameter body: A closure that gets scoped access to the inbound and outbound. | |||
@_disfavoredOverload |
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.
I think we should also deprecate those methods since in theory those are not concurrency safe and we want users to adopt a Swift 6 compiler to make those methods safe again.
Co-authored-by: Franz Busch <privat@franz-busch.de>
Co-authored-by: Franz Busch <privat@franz-busch.de>
Improve usability of NIOAsyncChannel.executeThenClose function from an Actor
Motivation:
When calling executeThenClose from inside an actor, the closure which we pass is not isolated to the same actor
This makes it hard to use
e.g. the following code won’t compile on Swift 6 because the closure is
self-isolated
so can’t be passedMaking the closure as @sendable allows it to be passed, but then we can’t access
group
anymoreModifications:
Added actor isolation parameter to the
executeThenClose
function.Result:
executeThenClose
will be able to be asynchronously called from inside an Actor, as it will now be part of the Actor's isolation domain.