diff --git a/stdlib/public/Concurrency/CMakeLists.txt b/stdlib/public/Concurrency/CMakeLists.txt index 443fb92a679d0..2e375e2d03861 100644 --- a/stdlib/public/Concurrency/CMakeLists.txt +++ b/stdlib/public/Concurrency/CMakeLists.txt @@ -63,6 +63,8 @@ endif() list(APPEND SWIFT_RUNTIME_CONCURRENCY_SWIFT_FLAGS "-enable-experimental-feature" "IsolatedAny" + "-enable-experimental-feature" + "ExecutionAttribute" ) list(APPEND SWIFT_RUNTIME_CONCURRENCY_SWIFT_FLAGS "-strict-memory-safety") diff --git a/stdlib/public/Concurrency/TaskCancellation.swift b/stdlib/public/Concurrency/TaskCancellation.swift index cd0a2bfaf5fb7..414d8674d3cef 100644 --- a/stdlib/public/Concurrency/TaskCancellation.swift +++ b/stdlib/public/Concurrency/TaskCancellation.swift @@ -68,9 +68,30 @@ import Swift /// not cancel tasks or resume continuations while holding that lock. @available(SwiftStdlib 5.1, *) #if !$Embedded -@backDeployed(before: SwiftStdlib 6.0) +@backDeployed(before: SwiftStdlib 6.2) #endif +nonisolated(nonsending) public func withTaskCancellationHandler( + operation: nonisolated(nonsending) () async throws -> T, + onCancel handler: @Sendable () -> Void +) async rethrows -> T { + // unconditionally add the cancellation record to the task. + // if the task was already cancelled, it will be executed right away. + let record = unsafe _taskAddCancellationHandler(handler: handler) + defer { unsafe _taskRemoveCancellationHandler(record: record) } + + return try await operation() +} + +// Note: Deprecated version which would still hop if we did not close over an `isolated` parameter +// with the operation closure. Instead, we should do what the docs of this method promise - and not hop at all, +// by using the new nonisolated(nonsending) +@available(SwiftStdlib 5.1, *) +//#if !$Embedded +//@backDeployed(before: SwiftStdlib 6.0) +//#endif +@_silgen_name("$ss27withTaskCancellationHandler9operation8onCancel9isolationxxyYaKXE_yyYbXEScA_pSgYitYaKlF") +public func _isolatedParam_withTaskCancellationHandler( operation: () async throws -> T, onCancel handler: @Sendable () -> Void, isolation: isolated (any Actor)? = #isolation diff --git a/test/Concurrency/Runtime/cancellation_handler_operation_does_not_hop.swift b/test/Concurrency/Runtime/cancellation_handler_operation_does_not_hop.swift new file mode 100644 index 0000000000000..5772a22ed0c99 --- /dev/null +++ b/test/Concurrency/Runtime/cancellation_handler_operation_does_not_hop.swift @@ -0,0 +1,60 @@ +// RUN: %target-run-simple-swift( -Xfrontend -disable-availability-checking -target %target-swift-5.1-abi-triple %import-libdispatch) | %FileCheck %s +// REQUIRES: concurrency +// REQUIRES: executable_test + +// REQUIRES: concurrency_runtime +// UNSUPPORTED: back_deployment_runtime +// UNSUPPORTED: freestanding + +actor Canceller { + func testFunc() async { + await withTaskCancellationHandler { + self.assertIsolated("wat in \(#function)!") + } onCancel: { + // noop + } + + await globalTestFunc() + } +} +func globalTestFunc(isolation: isolated (any Actor)? = #isolation) async { + isolation!.assertIsolated("wat in \(#function)!") + await withTaskCancellationHandler { + isolation!.assertIsolated("wat in \(#function)!") + } onCancel: { + // noop + } +} + +@MainActor +func testMainActor() async { + MainActor.preconditionIsolated("Expected main actor") + await withTaskCancellationHandler { + MainActor.preconditionIsolated("expected MainActor") + } onCancel: { + // noop + } +} + +func testMainActorIsolated(isolation: isolated (any Actor)? = #isolation) async { + isolation!.preconditionIsolated("Expected main actor") + MainActor.preconditionIsolated("Expected main actor") + await withTaskCancellationHandler { + print("_unsafeInheritExecutor_withTaskCancellationHandler") + MainActor.preconditionIsolated("expected MainActor") + } onCancel: { + // noop + } +} + +_ = await Canceller().testFunc() + +_ = await Task { @MainActor in + await testMainActor() +}.value + +_ = await Task { @MainActor in + await testMainActorIsolated() +}.value + +print("done") // CHECK: done diff --git a/test/abi/macOS/arm64/concurrency.swift b/test/abi/macOS/arm64/concurrency.swift index fc9420ad6174b..ba2287f851247 100644 --- a/test/abi/macOS/arm64/concurrency.swift +++ b/test/abi/macOS/arm64/concurrency.swift @@ -320,6 +320,12 @@ Added: _$sScf13checkIsolatedyyFTq // withTaskCancellationHandler gains #isolated Added: _$ss27withTaskCancellationHandler9operation8onCancel9isolationxxyYaKXE_yyYbXEScA_pSgYitYaKlF Added: _$ss27withTaskCancellationHandler9operation8onCancel9isolationxxyYaKXE_yyYbXEScA_pSgYitYaKlFTu +// withTaskCancellationHandler but with caller execution +// Swift.withTaskCancellationHandler(operation: nonisolated(nonsending) () async throws -> A, onCancel: @Sendable () -> ()) async throws -> A +// async function pointer to Swift.withTaskCancellationHandler(operation: nonisolated(nonsending) () async throws -> A, onCancel: @Sendable () -> ()) async throws -> A +Added: _$ss27withTaskCancellationHandler9operation8onCancelxxyYaKYCXE_yyYbXEtYaKlF +Added: _$ss27withTaskCancellationHandler9operation8onCancelxxyYaKYCXE_yyYbXEtYaKlFTu + // TaskGroup.with... APIs gain #isolated Added: _$ss23withDiscardingTaskGroup9returning9isolation4bodyxxm_ScA_pSgYixs0bcD0VzYaXEtYalF Added: _$ss23withDiscardingTaskGroup9returning9isolation4bodyxxm_ScA_pSgYixs0bcD0VzYaXEtYalFTu diff --git a/test/abi/macOS/x86_64/concurrency.swift b/test/abi/macOS/x86_64/concurrency.swift index 051b537ce5fd2..06812dc1432a1 100644 --- a/test/abi/macOS/x86_64/concurrency.swift +++ b/test/abi/macOS/x86_64/concurrency.swift @@ -320,6 +320,12 @@ Added: _$sScf13checkIsolatedyyFTq // withTaskCancellationHandler gains #isolated Added: _$ss27withTaskCancellationHandler9operation8onCancel9isolationxxyYaKXE_yyYbXEScA_pSgYitYaKlF Added: _$ss27withTaskCancellationHandler9operation8onCancel9isolationxxyYaKXE_yyYbXEScA_pSgYitYaKlFTu +// withTaskCancellationHandler but with caller execution +// Swift.withTaskCancellationHandler(operation: nonisolated(nonsending) () async throws -> A, onCancel: @Sendable () -> ()) async throws -> A +// async function pointer to Swift.withTaskCancellationHandler(operation: nonisolated(nonsending) () async throws -> A, onCancel: @Sendable () -> ()) async throws -> A +Added: _$ss27withTaskCancellationHandler9operation8onCancelxxyYaKYCXE_yyYbXEtYaKlF +Added: _$ss27withTaskCancellationHandler9operation8onCancelxxyYaKYCXE_yyYbXEtYaKlFTu + // TaskGroup.with... APIs gain #isolated Added: _$ss23withDiscardingTaskGroup9returning9isolation4bodyxxm_ScA_pSgYixs0bcD0VzYaXEtYalF Added: _$ss23withDiscardingTaskGroup9returning9isolation4bodyxxm_ScA_pSgYixs0bcD0VzYaXEtYalFTu diff --git a/test/api-digester/stability-concurrency-abi.test b/test/api-digester/stability-concurrency-abi.test index c9132ed6298ee..4a01626e6984c 100644 --- a/test/api-digester/stability-concurrency-abi.test +++ b/test/api-digester/stability-concurrency-abi.test @@ -88,10 +88,6 @@ Func withCheckedThrowingContinuation(function:_:) has mangled name changing from Func withCheckedThrowingContinuation(function:_:) has parameter 0 type change from Swift.String to (any _Concurrency.Actor)? 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' - // #isolated was adopted and the old methods kept: $ss31withCheckedThrowingContinuation8function_xSS_yScCyxs5Error_pGXEtYaKlF Func withCheckedContinuation(function:_:) has been renamed to Func withCheckedContinuation(isolation:function:_:) Func withCheckedContinuation(function:_:) has mangled name changing from '_Concurrency.withCheckedContinuation(function: Swift.String, _: (Swift.CheckedContinuation) -> ()) async -> A' to '_Concurrency.withCheckedContinuation(isolation: isolated Swift.Optional, function: Swift.String, _: (Swift.CheckedContinuation) -> ()) async -> A' @@ -128,6 +124,19 @@ Func withThrowingTaskGroup(of:returning:body:) has parameter 2 type change from Func withTaskGroup(of:returning:body:) has been renamed to Func withTaskGroup(of:returning:isolation:body:) Func withTaskGroup(of:returning:body:) has mangled name changing from '_Concurrency.withTaskGroup(of: A.Type, returning: B.Type, body: (inout Swift.TaskGroup) async -> B) async -> B' to '_Concurrency.withTaskGroup(of: A.Type, returning: B.Type, isolation: isolated Swift.Optional, body: (inout Swift.TaskGroup) async -> B) async -> B' +// Hashable for (Throwing)AsyncStream +// These are just @usableFromInline: +Var AsyncStream.Continuation.storage is a new API without '@available' +Var AsyncThrowingStream.Continuation.storage is a new API without '@available' +Class AsyncStream._Storage is a new API without '@available' +Class AsyncThrowingStream._Storage is a new API without '@available' + +// withTaskCancellationHandler now uses caller execution +// Old methods are kept for ABI compatibility but this test does not understand that +Func withTaskCancellationHandler(operation:onCancel:) has mangled name changing from '_Concurrency.withTaskCancellationHandler(operation: () async throws -> A, onCancel: @Sendable () -> ()) async throws -> A' to '_Concurrency.withTaskCancellationHandler(operation: nonisolated(nonsending) () async throws -> A, onCancel: @Sendable () -> ()) async throws -> A' +Func withTaskCancellationHandler(operation:onCancel:) is now with @execution + + // *** DO NOT DISABLE OR XFAIL THIS TEST. *** (See comment above.)