From 72f11bcfcbe667efe373a624ea6dfd76a1c8e43d Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 14 Nov 2025 14:45:37 -0800 Subject: [PATCH 1/3] [Concurrency] Adopt typed throws and nonisolated(nonsending) in withTaskCancellationHandler Replace the use of rethrows and #isolation in withTaskCancellationHandler with typed throws and nonisolated(nonsending), respectively. Fixes rdar://146901428. --- .../public/Concurrency/TaskCancellation.swift | 26 ++++++++++++++++--- test/Concurrency/async_cancellation.swift | 16 +++++++++--- .../stability-concurrency-abi.test | 4 +-- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/stdlib/public/Concurrency/TaskCancellation.swift b/stdlib/public/Concurrency/TaskCancellation.swift index b4d453e72651c..2991b4dfac3f0 100644 --- a/stdlib/public/Concurrency/TaskCancellation.swift +++ b/stdlib/public/Concurrency/TaskCancellation.swift @@ -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( + operation: () async throws(E) -> T, + onCancel handler: @Sendable () -> Void +) async throws(E) -> T { + // 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( +@available(SwiftStdlib 6.0, *) +@usableFromInline +@abi(func withTaskCancellationHandler( + operation: () async throws -> T, + onCancel handler: @Sendable () -> Void, + isolation: isolated (any Actor)?, + ) async rethrows -> T) +func __abi_withTaskCancellationHandler( operation: () async throws -> T, onCancel handler: @Sendable () -> Void, isolation: isolated (any Actor)? = #isolation @@ -89,6 +108,7 @@ public func withTaskCancellationHandler( return try await operation() } +#endif // Note: hack to stage out @_unsafeInheritExecutor forms of various functions // in favor of #isolation. The _unsafeInheritExecutor_ prefix is meaningful diff --git a/test/Concurrency/async_cancellation.swift b/test/Concurrency/async_cancellation.swift index 47f61fd3523e0..859c3a519297d 100644 --- a/test/Concurrency/async_cancellation.swift +++ b/test/Concurrency/async_cancellation.swift @@ -28,15 +28,23 @@ struct SomeFile: Sendable { func close() {} } +enum HomeworkError: Error { +case dogAteIt +} + @available(SwiftStdlib 5.1, *) func test_cancellation_withTaskCancellationHandler(_ anything: Any) async -> PictureData? { let handle: Task = .init { let file = SomeFile() - return await withTaskCancellationHandler { - await test_cancellation_guard_isCancelled(file) - } onCancel: { - file.close() + do throws(HomeworkError) { + return try await withTaskCancellationHandler { () throws(HomeworkError) in + await test_cancellation_guard_isCancelled(file) + } onCancel: { + file.close() + } + } catch .dogAteIt { + return PictureData.value("...") } } diff --git a/test/api-digester/stability-concurrency-abi.test b/test/api-digester/stability-concurrency-abi.test index 8e8e24505f24f..61a31426a612f 100644 --- a/test/api-digester/stability-concurrency-abi.test +++ b/test/api-digester/stability-concurrency-abi.test @@ -89,8 +89,8 @@ Func withCheckedThrowingContinuation(function:_:) has parameter 0 type change fr Func withCheckedThrowingContinuation(function:_:) has parameter 1 type change from (_Concurrency.CheckedContinuation<τ_0_0, any Swift.Error>) -> () to Swift.String // #isolation adoption for cancellation handlers; old APIs are kept ABI compatible -Func withTaskCancellationHandler(operation:onCancel:) has been renamed to Func withTaskCancellationHandler(operation:onCancel:isolation:) -Func withTaskCancellationHandler(operation:onCancel:) has mangled name changing from '_Concurrency.withTaskCancellationHandler(operation: () async throws -> A, onCancel: @Sendable () -> ()) async throws -> A' to '_Concurrency.withTaskCancellationHandler(operation: () async throws -> A, onCancel: @Sendable () -> (), isolation: isolated Swift.Optional) async throws -> A' +Func withTaskCancellationHandler(operation:onCancel:) has been renamed to Func __abi_withTaskCancellationHandler(operation:onCancel:isolation:) +Func withTaskCancellationHandler(operation:onCancel:) has mangled name changing from '_Concurrency.withTaskCancellationHandler(operation: () async throws -> A, onCancel: @Sendable () -> ()) async throws -> A' to '_Concurrency.__abi_withTaskCancellationHandler(operation: () async throws -> A, onCancel: @Sendable () -> (), isolation: isolated Swift.Optional) async throws -> A' // #isolated was adopted and the old methods kept: $ss31withCheckedThrowingContinuation8function_xSS_yScCyxs5Error_pGXEtYaKlF Func withCheckedContinuation(function:_:) has been renamed to Func withCheckedContinuation(isolation:function:_:) From f8265e645507b206a56f5ab165193c0b9f7124c5 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 14 Nov 2025 16:14:10 -0800 Subject: [PATCH 2/3] Drop extraneous `isolation` argument --- .../public/Observation/Sources/Observation/Observations.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/public/Observation/Sources/Observation/Observations.swift b/stdlib/public/Observation/Sources/Observation/Observations.swift index 3aa6d91ba56c6..8d652f1e13f6b 100644 --- a/stdlib/public/Observation/Sources/Observation/Observations.swift +++ b/stdlib/public/Observation/Sources/Observation/Observations.swift @@ -240,7 +240,7 @@ public struct Observations: AsyncSequence, Se }, onCancel: { // ensure to clean out our continuation uon cancellation State.cancel(state, id: id) - }, isolation: iterationIsolation) + }) return try await trackEmission(isolation: iterationIsolation, state: state, id: id) } } catch { From db52e86b212b5199318dc95eaa3c600efff7fa34 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 18 Nov 2025 13:31:30 -0800 Subject: [PATCH 3/3] [Task cancellation] Adopt nonisolated(nonsending) and sending --- stdlib/public/Concurrency/TaskCancellation.swift | 9 ++++----- test/Concurrency/actor_withCancellationHandler.swift | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/stdlib/public/Concurrency/TaskCancellation.swift b/stdlib/public/Concurrency/TaskCancellation.swift index 2991b4dfac3f0..9006fbed306dd 100644 --- a/stdlib/public/Concurrency/TaskCancellation.swift +++ b/stdlib/public/Concurrency/TaskCancellation.swift @@ -76,15 +76,14 @@ import Swift @available(SwiftStdlib 5.1, *) @export(implementation) nonisolated(nonsending) -public func withTaskCancellationHandler( - operation: () async throws(E) -> T, - onCancel handler: @Sendable () -> Void -) async throws(E) -> T { +public func withTaskCancellationHandler( + operation: nonisolated(nonsending) () async throws(Failure) -> Return, + onCancel handler: sending () -> Void +) async throws(Failure) -> Return { // 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() } diff --git a/test/Concurrency/actor_withCancellationHandler.swift b/test/Concurrency/actor_withCancellationHandler.swift index 39e0dca72f41c..c4b95c3e9133c 100644 --- a/test/Concurrency/actor_withCancellationHandler.swift +++ b/test/Concurrency/actor_withCancellationHandler.swift @@ -38,7 +38,7 @@ actor Container { num += 1 // no error, this runs synchronously on caller context } onCancel: { // this should error because cancellation is invoked concurrently - num += 10 // expected-error{{actor-isolated property 'num' can not be mutated from a Sendable closure}} + num += 10 // expected-error{{actor-isolated property 'num' can not be mutated from a nonisolated context}} } } }