-
Notifications
You must be signed in to change notification settings - Fork 10.6k
[Concurrency] Adopt typed throws and nonisolated(nonsending) in withTaskCancellationHandler #85518
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -74,10 +74,29 @@ import Swift | |||||
| /// Therefore, if a cancellation handler must acquire a lock, other code should | ||||||
| /// not cancel tasks or resume continuations while holding that lock. | ||||||
| @available(SwiftStdlib 5.1, *) | ||||||
| @export(implementation) | ||||||
| nonisolated(nonsending) | ||||||
| public func withTaskCancellationHandler<T, E>( | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NIT: Could we give these generic types a better name so that they show up nicely in docs or would that be API breaking?
Suggested change
|
||||||
| operation: () async throws(E) -> T, | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this closure also be
Suggested change
|
||||||
| onCancel handler: @Sendable () -> Void | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't this closure be |
||||||
| ) async throws(E) -> T { | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we have any tests that exercise the case described here? func next(isolation: isolated (any Actor)? = #isolation) async {
MainActor.assertIsolated() // passes
await withTaskCancellationHandler {
MainActor.assertIsolated() // fails!
} onCancel: { }
}
@MainActor
func callOnMain() async {
await next()
}is the outcome in that scenario expected to be different now? or will the same issue occur depending on how the closure isolation is inferred?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This has been a long standing bug. We always "promised" that these with methods would never lose isolation however this didn't actually work correctly with the isolated parameter... This is finally bringing the correct behavior to these APIs. All the with methods have the same issue and we'll change all of them to now give the behavior we've always promised to begin with
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe i'm confused as to what issue is being targeted here. my understanding is that the existing signature (prior to a conversion to it seems like this change alone (ignoring typed throws) will not alter the behavior of this API – if the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, that is the (very long standing) bug, the async closure still would hop: this is very much incorrect and should have never been happening to hop off, but it does. The way closure isolation works just isn't correct here and the "real" solution is to move all these with... APIs to nonisolated nonsending.
It does change behavior; it corrects the unexpected hopping off in certain scenarios -- one of which you've just pasted, that is incorrect and we must fix it; it breaks the safety contract of the withTaskCancellationHandler function
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
focusing specifically on the change of the function signature to admittedly i have not applied these changes locally to experiment, but if you make a function with a similar signature, it does not appear that without the is that class of examples, the only way in which isolation can be 'lost' or are there others? anyway, i apologize for the intrusion as this is probably not the appropriate venue to have this sort of discussion. to briefly return to my original motivation – if this change is anticipated to fix a known bug, it seems prudent to have tests that demonstrate the fix works as intended. |
||||||
| // unconditionally add the cancellation record to the task. | ||||||
| // if the task was already cancelled, it will be executed right away. | ||||||
| let record = unsafe Builtin.taskAddCancellationHandler(handler: handler) | ||||||
| defer { unsafe Builtin.taskRemoveCancellationHandler(record: record) } | ||||||
|
|
||||||
| return try await operation() | ||||||
| } | ||||||
|
|
||||||
| #if !$Embedded | ||||||
| @backDeployed(before: SwiftStdlib 6.0) | ||||||
| #endif | ||||||
| public func withTaskCancellationHandler<T>( | ||||||
| @available(SwiftStdlib 6.0, *) | ||||||
| @usableFromInline | ||||||
| @abi(func withTaskCancellationHandler<T>( | ||||||
| operation: () async throws -> T, | ||||||
| onCancel handler: @Sendable () -> Void, | ||||||
| isolation: isolated (any Actor)?, | ||||||
| ) async rethrows -> T) | ||||||
| func __abi_withTaskCancellationHandler<T>( | ||||||
| operation: () async throws -> T, | ||||||
| onCancel handler: @Sendable () -> Void, | ||||||
| isolation: isolated (any Actor)? = #isolation | ||||||
|
|
@@ -89,6 +108,7 @@ public func withTaskCancellationHandler<T>( | |||||
|
|
||||||
| return try await operation() | ||||||
| } | ||||||
| #endif | ||||||
|
|
||||||
| // Note: hack to stage out @_unsafeInheritExecutor forms of various functions | ||||||
| // in favor of #isolation. The _unsafeInheritExecutor_ prefix is meaningful | ||||||
|
|
||||||
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.
Could we add a TODO/FIXME with a reminder to replace this with
@backDeployed(before: Swift 6.4)at some point?