Skip to content

Commit de601db

Browse files
committed
[Concurrency] Use caller execution for withTaskCannelation handler
The problem is that even with the #isolation parameter the non-Sendable async closure operation _still_ would potentially hop off the caller isolation. We introduced this change because the plan was the non-Sendable closure would run on the isolated parameter's isolation, but that's not actually the case: Instead, we can use the @execution(caller) on the function and closure, in order to guarantee there is no hop between those at all, and developers can trust that adding this cancellation handler will not cause any unexpected isolation changes and hops. The API was always documented to not hop as we execute the operation, so this brings the correct and expected behavior. resolves rdar://140110775
1 parent 6e51dd8 commit de601db

File tree

6 files changed

+101
-5
lines changed

6 files changed

+101
-5
lines changed

Diff for: stdlib/public/Concurrency/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ endif()
6363
list(APPEND SWIFT_RUNTIME_CONCURRENCY_SWIFT_FLAGS
6464
"-enable-experimental-feature"
6565
"IsolatedAny"
66+
"-enable-experimental-feature"
67+
"ExecutionAttribute"
6668
)
6769

6870
list(APPEND SWIFT_RUNTIME_CONCURRENCY_SWIFT_FLAGS "-strict-memory-safety")

Diff for: stdlib/public/Concurrency/TaskCancellation.swift

+22-1
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,30 @@ import Swift
6868
/// not cancel tasks or resume continuations while holding that lock.
6969
@available(SwiftStdlib 5.1, *)
7070
#if !$Embedded
71-
@backDeployed(before: SwiftStdlib 6.0)
71+
@backDeployed(before: SwiftStdlib 6.2)
7272
#endif
73+
@execution(caller)
7374
public func withTaskCancellationHandler<T>(
75+
operation: @execution(caller) () async throws -> T,
76+
onCancel handler: @Sendable () -> Void
77+
) async rethrows -> T {
78+
// unconditionally add the cancellation record to the task.
79+
// if the task was already cancelled, it will be executed right away.
80+
let record = unsafe _taskAddCancellationHandler(handler: handler)
81+
defer { unsafe _taskRemoveCancellationHandler(record: record) }
82+
83+
return try await operation()
84+
}
85+
86+
// Note: Deprecated version which would still hop if we did not close over an `isolated` parameter
87+
// with the operation closure. Instead, we should do what the docs of this method promise - and not hop at all,
88+
// by using the new @execution(caller)
89+
@available(SwiftStdlib 5.1, *)
90+
//#if !$Embedded
91+
//@backDeployed(before: SwiftStdlib 6.0)
92+
//#endif
93+
@_silgen_name("$ss27withTaskCancellationHandler9operation8onCancel9isolationxxyYaKXE_yyYbXEScA_pSgYitYaKlF")
94+
public func _isolatedParam_withTaskCancellationHandler<T>(
7495
operation: () async throws -> T,
7596
onCancel handler: @Sendable () -> Void,
7697
isolation: isolated (any Actor)? = #isolation
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// RUN: %target-run-simple-swift( -Xfrontend -disable-availability-checking -target %target-swift-5.1-abi-triple %import-libdispatch) | %FileCheck %s
2+
// REQUIRES: concurrency
3+
// REQUIRES: executable_test
4+
5+
// REQUIRES: concurrency_runtime
6+
// UNSUPPORTED: back_deployment_runtime
7+
// UNSUPPORTED: freestanding
8+
9+
actor Canceller {
10+
func testFunc() async {
11+
await withTaskCancellationHandler {
12+
self.assertIsolated("wat in \(#function)!")
13+
} onCancel: {
14+
// noop
15+
}
16+
17+
await globalTestFunc()
18+
}
19+
}
20+
func globalTestFunc(isolation: isolated (any Actor)? = #isolation) async {
21+
isolation!.assertIsolated("wat in \(#function)!")
22+
await withTaskCancellationHandler {
23+
isolation!.assertIsolated("wat in \(#function)!")
24+
} onCancel: {
25+
// noop
26+
}
27+
}
28+
29+
@MainActor
30+
func testMainActor() async {
31+
MainActor.preconditionIsolated("Expected main actor")
32+
await withTaskCancellationHandler {
33+
// MainActor.preconditionIsolated("expected MainActor")
34+
} onCancel: {
35+
// noop
36+
}
37+
}
38+
39+
func testMainActorIsolated(isolation: isolated (any Actor)? = #isolation) async {
40+
isolation!.preconditionIsolated("Expected main actor")
41+
MainActor.preconditionIsolated("Expected main actor")
42+
await withTaskCancellationHandler {
43+
print("_unsafeInheritExecutor_withTaskCancellationHandler")
44+
// MainActor.preconditionIsolated("expected MainActor")
45+
} onCancel: {
46+
// noop
47+
}
48+
}
49+
50+
_ = await Canceller().testFunc()
51+
52+
_ = await Task { @MainActor in
53+
await testMainActor()
54+
}.value
55+
56+
_ = await Task { @MainActor in
57+
await testMainActorIsolated()
58+
}.value
59+
60+
print("done") // CHECK: done

Diff for: test/abi/macOS/arm64/concurrency.swift

+6
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,12 @@ Added: _$sScf13checkIsolatedyyFTq
320320
// withTaskCancellationHandler gains #isolated
321321
Added: _$ss27withTaskCancellationHandler9operation8onCancel9isolationxxyYaKXE_yyYbXEScA_pSgYitYaKlF
322322
Added: _$ss27withTaskCancellationHandler9operation8onCancel9isolationxxyYaKXE_yyYbXEScA_pSgYitYaKlFTu
323+
// withTaskCancellationHandler but with caller execution
324+
// Swift.withTaskCancellationHandler<A>(operation: @execution(caller) () async throws -> A, onCancel: @Sendable () -> ()) async throws -> A
325+
// async function pointer to Swift.withTaskCancellationHandler<A>(operation: @execution(caller) () async throws -> A, onCancel: @Sendable () -> ()) async throws -> A
326+
Added: _$ss27withTaskCancellationHandler9operation8onCancelxxyYaKYCXE_yyYbXEtYaKlF
327+
Added: _$ss27withTaskCancellationHandler9operation8onCancelxxyYaKYCXE_yyYbXEtYaKlFTu
328+
323329
// TaskGroup.with... APIs gain #isolated
324330
Added: _$ss23withDiscardingTaskGroup9returning9isolation4bodyxxm_ScA_pSgYixs0bcD0VzYaXEtYalF
325331
Added: _$ss23withDiscardingTaskGroup9returning9isolation4bodyxxm_ScA_pSgYixs0bcD0VzYaXEtYalFTu

Diff for: test/abi/macOS/x86_64/concurrency.swift

+6
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,12 @@ Added: _$sScf13checkIsolatedyyFTq
320320
// withTaskCancellationHandler gains #isolated
321321
Added: _$ss27withTaskCancellationHandler9operation8onCancel9isolationxxyYaKXE_yyYbXEScA_pSgYitYaKlF
322322
Added: _$ss27withTaskCancellationHandler9operation8onCancel9isolationxxyYaKXE_yyYbXEScA_pSgYitYaKlFTu
323+
// withTaskCancellationHandler but with caller execution
324+
// Swift.withTaskCancellationHandler<A>(operation: @execution(caller) () async throws -> A, onCancel: @Sendable () -> ()) async throws -> A
325+
// async function pointer to Swift.withTaskCancellationHandler<A>(operation: @execution(caller) () async throws -> A, onCancel: @Sendable () -> ()) async throws -> A
326+
Added: _$ss27withTaskCancellationHandler9operation8onCancelxxyYaKYCXE_yyYbXEtYaKlF
327+
Added: _$ss27withTaskCancellationHandler9operation8onCancelxxyYaKYCXE_yyYbXEtYaKlFTu
328+
323329
// TaskGroup.with... APIs gain #isolated
324330
Added: _$ss23withDiscardingTaskGroup9returning9isolation4bodyxxm_ScA_pSgYixs0bcD0VzYaXEtYalF
325331
Added: _$ss23withDiscardingTaskGroup9returning9isolation4bodyxxm_ScA_pSgYixs0bcD0VzYaXEtYalFTu

Diff for: test/api-digester/stability-concurrency-abi.test

+5-4
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,6 @@ Func withCheckedThrowingContinuation(function:_:) has mangled name changing from
8888
Func withCheckedThrowingContinuation(function:_:) has parameter 0 type change from Swift.String to (any _Concurrency.Actor)?
8989
Func withCheckedThrowingContinuation(function:_:) has parameter 1 type change from (_Concurrency.CheckedContinuation<τ_0_0, any Swift.Error>) -> () to Swift.String
9090

91-
// #isolation adoption for cancellation handlers; old APIs are kept ABI compatible
92-
Func withTaskCancellationHandler(operation:onCancel:) has been renamed to Func withTaskCancellationHandler(operation:onCancel:isolation:)
93-
Func withTaskCancellationHandler(operation:onCancel:) has mangled name changing from '_Concurrency.withTaskCancellationHandler<A>(operation: () async throws -> A, onCancel: @Sendable () -> ()) async throws -> A' to '_Concurrency.withTaskCancellationHandler<A>(operation: () async throws -> A, onCancel: @Sendable () -> (), isolation: isolated Swift.Optional<Swift.Actor>) async throws -> A'
94-
9591
// #isolated was adopted and the old methods kept: $ss31withCheckedThrowingContinuation8function_xSS_yScCyxs5Error_pGXEtYaKlF
9692
Func withCheckedContinuation(function:_:) has been renamed to Func withCheckedContinuation(isolation:function:_:)
9793
Func withCheckedContinuation(function:_:) has mangled name changing from '_Concurrency.withCheckedContinuation<A>(function: Swift.String, _: (Swift.CheckedContinuation<A, Swift.Never>) -> ()) async -> A' to '_Concurrency.withCheckedContinuation<A>(isolation: isolated Swift.Optional<Swift.Actor>, function: Swift.String, _: (Swift.CheckedContinuation<A, Swift.Never>) -> ()) async -> A'
@@ -128,6 +124,11 @@ Func withThrowingTaskGroup(of:returning:body:) has parameter 2 type change from
128124
Func withTaskGroup(of:returning:body:) has been renamed to Func withTaskGroup(of:returning:isolation:body:)
129125
Func withTaskGroup(of:returning:body:) has mangled name changing from '_Concurrency.withTaskGroup<A, B where A: Swift.Sendable>(of: A.Type, returning: B.Type, body: (inout Swift.TaskGroup<A>) async -> B) async -> B' to '_Concurrency.withTaskGroup<A, B where A: Swift.Sendable>(of: A.Type, returning: B.Type, isolation: isolated Swift.Optional<Swift.Actor>, body: (inout Swift.TaskGroup<A>) async -> B) async -> B'
130126

127+
// withTaskCancellationHandler now uses caller execution
128+
// Old methods are kept for ABI compatibility but this test does not understand that
129+
Func withTaskCancellationHandler(operation:onCancel:) has mangled name changing from '_Concurrency.withTaskCancellationHandler<A>(operation: () async throws -> A, onCancel: @Sendable () -> ()) async throws -> A' to '_Concurrency.withTaskCancellationHandler<A>(operation: @execution(caller) () async throws -> A, onCancel: @Sendable () -> ()) async throws -> A'
130+
Func withTaskCancellationHandler(operation:onCancel:) is now with @execution
131+
131132
// *** DO NOT DISABLE OR XFAIL THIS TEST. *** (See comment above.)
132133

133134

0 commit comments

Comments
 (0)