From ebc3d23c01db140c51a588e3d8bc413d39cc6ed6 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Tue, 4 Jun 2024 09:21:30 +0900 Subject: [PATCH 1/3] [Concurrency] Typed throws in Task.init and .detached --- stdlib/public/Concurrency/Task.swift | 210 ++++++++++++++++++++++++++- test/Concurrency/typed_throws.swift | 18 +++ 2 files changed, 227 insertions(+), 1 deletion(-) diff --git a/stdlib/public/Concurrency/Task.swift b/stdlib/public/Concurrency/Task.swift index f81c23d2c72d6..d5058e96111a2 100644 --- a/stdlib/public/Concurrency/Task.swift +++ b/stdlib/public/Concurrency/Task.swift @@ -167,6 +167,19 @@ extension Task { /// /// - Returns: The task's result. public var value: Success { + get async throws(Failure) { + do { + return try await __abi_value + } catch { + throw error as! Failure + } + } + } + + @available(SwiftStdlib 5.1, *) + @_silgen_name("$sScT5valuexvg") + @usableFromInline + internal var __abi_value: Success { get async throws { return try await _taskFutureGetThrowing(_task) } @@ -187,7 +200,7 @@ extension Task { public var result: Result { get async { do { - return .success(try await value) + return .success(try await __abi_value) } catch { return .failure(error as! Failure) // as!-safe, guaranteed to be Failure } @@ -790,6 +803,104 @@ extension Task where Failure == Error { #endif } +// ==== Typed throws Task.init overloads --------------------------------------- + +@available(SwiftStdlib 6.0, *) +extension Task { +#if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY + @discardableResult + @_alwaysEmitIntoClient + @_allowFeatureSuppression(IsolatedAny) + @available(*, unavailable, message: "Unavailable in task-to-thread concurrency model") + public init( + priority: TaskPriority? = nil, + @_inheritActorContext @_implicitSelfCapture operation: __owned @Sendable @escaping @isolated(any) () async throws(Failure) -> Success + ) { + fatalError("Unavailable in task-to-thread concurrency model") + } +#elseif $Embedded + @discardableResult + @_alwaysEmitIntoClient + public init( + priority: TaskPriority? = nil, + @_inheritActorContext @_implicitSelfCapture operation: __owned @Sendable @escaping @isolated(any) () async throws(Failure) -> Success + ) { +#if compiler(>=5.5) && $BuiltinCreateAsyncTaskInGroup + // Set up the task flags for a new task. + let flags = taskCreateFlags( + priority: priority, isChildTask: false, copyTaskLocals: true, + inheritContext: true, enqueueJob: true, + addPendingGroupTaskUnconditionally: false, + isDiscardingTask: false) + + // Create the asynchronous task future. + let (task, _) = Builtin.createAsyncTask(flags, operation) + + self._task = task +#else + fatalError("Unsupported Swift compiler") +#endif + } +#else // if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY + /// Runs the given operation asynchronously + /// as part of a new top-level task on behalf of the current actor. + /// + /// Use this function when creating asynchronous work + /// that operates on behalf of the synchronous function that calls it. + /// Like `Task.detached(priority:operation:)`, + /// this function creates a separate, top-level task. + /// Unlike `detach(priority:operation:)`, + /// the task created by `Task.init(priority:operation:)` + /// inherits the priority and actor context of the caller, + /// so the operation is treated more like an asynchronous extension + /// to the synchronous operation. + /// + /// You need to keep a reference to the task + /// if you want to cancel it by calling the `Task.cancel()` method. + /// Discarding your reference to a detached task + /// doesn't implicitly cancel that task, + /// it only makes it impossible for you to explicitly cancel the task. + /// + /// - Parameters: + /// - priority: The priority of the task. + /// Pass `nil` to use the priority from `Task.currentPriority`. + /// - operation: The operation to perform. + @discardableResult + @_alwaysEmitIntoClient + @_allowFeatureSuppression(IsolatedAny) + public init( + priority: TaskPriority? = nil, + @_inheritActorContext @_implicitSelfCapture operation: __owned @Sendable @escaping @isolated(any) () async throws(Failure) -> Success + ) { +#if compiler(>=5.5) && $BuiltinCreateAsyncTaskInGroup + // Set up the task flags for a new task. + let flags = taskCreateFlags( + priority: priority, isChildTask: false, copyTaskLocals: true, + inheritContext: true, enqueueJob: true, + addPendingGroupTaskUnconditionally: false, + isDiscardingTask: false) + + // Create the asynchronous task future. +#if $BuiltinCreateTask + let builtinSerialExecutor = + Builtin.extractFunctionIsolation(operation)?.unownedExecutor.executor + + let (task, _) = Builtin.createTask(flags: flags, + initialSerialExecutor: + builtinSerialExecutor, + operation: operation) +#else + let (task, _) = Builtin.createAsyncTask(flags, operation) +#endif + + self._task = task +#else + fatalError("Unsupported Swift compiler") +#endif + } +#endif +} + // ==== Detached Tasks --------------------------------------------------------- @available(SwiftStdlib 5.1, *) @@ -980,6 +1091,103 @@ extension Task where Failure == Error { #endif } +// ==== Typed throws Task.detached overloads ----------------------------------- + +@available(SwiftStdlib 6.0, *) +extension Task { +#if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY + @discardableResult + @_alwaysEmitIntoClient + @_allowFeatureSuppression(IsolatedAny) + @available(*, unavailable, message: "Unavailable in task-to-thread concurrency model") + public static func detached( + priority: TaskPriority? = nil, + operation: __owned @Sendable @escaping @isolated(any) () async throws(Failure) -> Success + ) -> Task { + fatalError("Unavailable in task-to-thread concurrency model") + } +#elseif $Embedded + @discardableResult + @_alwaysEmitIntoClient + public static func detached( + priority: TaskPriority? = nil, + operation: __owned @Sendable @escaping () async throws(Failure) -> Success + ) -> Task { +#if compiler(>=5.5) && $BuiltinCreateAsyncTaskInGroup + // Set up the job flags for a new task. + let flags = taskCreateFlags( + priority: priority, isChildTask: false, copyTaskLocals: false, + inheritContext: false, enqueueJob: true, + addPendingGroupTaskUnconditionally: false, + isDiscardingTask: false) + + // Create the asynchronous task future. + let (task, _) = Builtin.createAsyncTask(flags, operation) + + return Task(task) +#else + fatalError("Unsupported Swift compiler") +#endif + } +#else + /// Runs the given throwing operation asynchronously + /// as part of a new top-level task. + /// + /// If the operation throws an error, this method propagates that error. + /// + /// Don't use a detached task if it's possible + /// to model the operation using structured concurrency features like child tasks. + /// Child tasks inherit the parent task's priority and task-local storage, + /// and canceling a parent task automatically cancels all of its child tasks. + /// You need to handle these considerations manually with a detached task. + /// + /// You need to keep a reference to the detached task + /// if you want to cancel it by calling the `Task.cancel()` method. + /// Discarding your reference to a detached task + /// doesn't implicitly cancel that task, + /// it only makes it impossible for you to explicitly cancel the task. + /// + /// - Parameters: + /// - priority: The priority of the task. + /// - operation: The operation to perform. + /// + /// - Returns: A reference to the task. + @discardableResult + @_alwaysEmitIntoClient + @_allowFeatureSuppression(IsolatedAny) + public static func detached( + priority: TaskPriority? = nil, + operation: __owned @Sendable @escaping @isolated(any) () async throws(Failure) -> Success + ) -> Task { +#if compiler(>=5.5) && $BuiltinCreateAsyncTaskInGroup + // Set up the job flags for a new task. + let flags = taskCreateFlags( + priority: priority, isChildTask: false, copyTaskLocals: false, + inheritContext: false, enqueueJob: true, + addPendingGroupTaskUnconditionally: false, + isDiscardingTask: false) + + // Create the asynchronous task future. +#if $BuiltinCreateTask + let builtinSerialExecutor = + Builtin.extractFunctionIsolation(operation)?.unownedExecutor.executor + + let (task, _) = Builtin.createTask(flags: flags, + initialSerialExecutor: + builtinSerialExecutor, + operation: operation) +#else + let (task, _) = Builtin.createAsyncTask(flags, operation) +#endif + + return Task(task) +#else + fatalError("Unsupported Swift compiler") +#endif + } +#endif +} + // ==== Voluntary Suspension ----------------------------------------------------- @available(SwiftStdlib 5.1, *) diff --git a/test/Concurrency/typed_throws.swift b/test/Concurrency/typed_throws.swift index 621d3b1f01e7c..91dbf91d71e0b 100644 --- a/test/Concurrency/typed_throws.swift +++ b/test/Concurrency/typed_throws.swift @@ -21,3 +21,21 @@ func testAsyncFor(seq: S) async throws(MyError) for try await _ in seq { } } + +@available(SwiftStdlib 6.0, *) +func testTask() async throws(MyError) { + let t: Task = Task { () throws(MyError) -> Int in + throw MyError.failed + } + + _ = try await t.value +} + +@available(SwiftStdlib 6.0, *) +func testTaskDetached() async throws(MyError) { + let t: Task = Task.detached { () throws(MyError) -> Int in + throw MyError.failed + } + + _ = try await t.value +} From bc028dcb41e218db8602b8e24e646a7aed8e0709 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Tue, 4 Jun 2024 09:59:03 +0900 Subject: [PATCH 2/3] bin compat of .value accessor a bit confusing; shouldnt the typed throw be mangled differently? --- stdlib/public/Concurrency/Task.swift | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/stdlib/public/Concurrency/Task.swift b/stdlib/public/Concurrency/Task.swift index d5058e96111a2..4f06ac97f67a8 100644 --- a/stdlib/public/Concurrency/Task.swift +++ b/stdlib/public/Concurrency/Task.swift @@ -167,24 +167,17 @@ extension Task { /// /// - Returns: The task's result. public var value: Success { + // FIXME: This seems wrong that abi test is not freaking out here? + // At the same time, adding a silgen_name compat accessor with old signature results in duplicate definitions -- is mangling of typed throws not done properly on return position? get async throws(Failure) { do { - return try await __abi_value + return try await _taskFutureGetThrowing(_task) } catch { throw error as! Failure } } } - @available(SwiftStdlib 5.1, *) - @_silgen_name("$sScT5valuexvg") - @usableFromInline - internal var __abi_value: Success { - get async throws { - return try await _taskFutureGetThrowing(_task) - } - } - /// The result or error from a throwing task, after it completes. /// /// If the task hasn't completed, @@ -200,7 +193,7 @@ extension Task { public var result: Result { get async { do { - return .success(try await __abi_value) + return .success(try await value) } catch { return .failure(error as! Failure) // as!-safe, guaranteed to be Failure } From ef7bb797af55cf56cf8d6ffbaa4e935e66cacc3b Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 5 Jun 2024 21:33:04 +0900 Subject: [PATCH 3/3] [Concurrency] ABI compat of typed throws and Task.value --- stdlib/public/Concurrency/Task.swift | 15 ++++++++++++--- test/abi/macOS/arm64/concurrency.swift | 5 ++++- test/abi/macOS/x86_64/concurrency.swift | 4 ++++ test/api-digester/stability-concurrency-abi.test | 6 ++++++ 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/stdlib/public/Concurrency/Task.swift b/stdlib/public/Concurrency/Task.swift index 4f06ac97f67a8..faaf6d9d7a376 100644 --- a/stdlib/public/Concurrency/Task.swift +++ b/stdlib/public/Concurrency/Task.swift @@ -166,9 +166,9 @@ extension Task { /// have that error propagated here upon cancellation. /// /// - Returns: The task's result. + @_alwaysEmitIntoClient public var value: Success { - // FIXME: This seems wrong that abi test is not freaking out here? - // At the same time, adding a silgen_name compat accessor with old signature results in duplicate definitions -- is mangling of typed throws not done properly on return position? + @_silgen_name("$sScT7valueTTxvg") // "_t" suffix for the typed throws version get async throws(Failure) { do { return try await _taskFutureGetThrowing(_task) @@ -178,6 +178,15 @@ extension Task { } } + // Legacy non-typed throws computed property + @usableFromInline + internal var __abi_value: Success { + @_silgen_name("$sScT5valuexvg") + get async throws { + return try await _taskFutureGetThrowing(_task) + } + } + /// The result or error from a throwing task, after it completes. /// /// If the task hasn't completed, @@ -195,7 +204,7 @@ extension Task { do { return .success(try await value) } catch { - return .failure(error as! Failure) // as!-safe, guaranteed to be Failure + return .failure(error) } } } diff --git a/test/abi/macOS/arm64/concurrency.swift b/test/abi/macOS/arm64/concurrency.swift index 157ad11625b4d..a68bee073ed97 100644 --- a/test/abi/macOS/arm64/concurrency.swift +++ b/test/abi/macOS/arm64/concurrency.swift @@ -270,6 +270,10 @@ Added: _swift_task_getPreferredTaskExecutor Added: _swift_task_popTaskExecutorPreference Added: _swift_task_pushTaskExecutorPreference +// Typed throws Task +// property descriptor for Swift.Task.__abi_value : A +Added: _$sScT11__abi_valuexvpMV + // Adopt #isolation in with...Continuation APIs // Swift.withCheckedThrowingContinuation(isolation: isolated Swift.Actor?, function: Swift.String, _: (Swift.CheckedContinuation) -> ()) async throws -> A Added: _$ss31withCheckedThrowingContinuation9isolation8function_xScA_pSgYi_SSyScCyxs5Error_pGXEtYaKlF @@ -322,4 +326,3 @@ Added: _$ss9TaskLocalC13withValueImpl_9operation9isolation4file4lineqd__xn_qd__y // Swift.TaskLocal.withValue(_: A, operation: () async throws -> A1, isolation: isolated Swift.Actor?, file: Swift.String, line: Swift.UInt) async throws -> A1 Added: _$ss9TaskLocalC9withValue_9operation9isolation4file4lineqd__x_qd__yYaKXEScA_pSgYiSSSutYaKlF Added: _$ss9TaskLocalC9withValue_9operation9isolation4file4lineqd__x_qd__yYaKXEScA_pSgYiSSSutYaKlFTu - diff --git a/test/abi/macOS/x86_64/concurrency.swift b/test/abi/macOS/x86_64/concurrency.swift index 330469a3c5af6..903f4a19d6e11 100644 --- a/test/abi/macOS/x86_64/concurrency.swift +++ b/test/abi/macOS/x86_64/concurrency.swift @@ -270,6 +270,10 @@ Added: _swift_task_getPreferredTaskExecutor Added: _swift_task_popTaskExecutorPreference Added: _swift_task_pushTaskExecutorPreference +// Typed throws Task +// property descriptor for Swift.Task.__abi_value : A +Added: _$sScT11__abi_valuexvpMV + // Adopt #isolation in with...Continuation APIs // Swift.withCheckedThrowingContinuation(isolation: isolated Swift.Actor?, function: Swift.String, _: (Swift.CheckedContinuation) -> ()) async throws -> A Added: _$ss31withCheckedThrowingContinuation9isolation8function_xScA_pSgYi_SSyScCyxs5Error_pGXEtYaKlF diff --git a/test/api-digester/stability-concurrency-abi.test b/test/api-digester/stability-concurrency-abi.test index a49455914ff8b..173eb66d833de 100644 --- a/test/api-digester/stability-concurrency-abi.test +++ b/test/api-digester/stability-concurrency-abi.test @@ -115,6 +115,12 @@ Func TaskLocal.withValue(_:operation:file:line:) has parameter 1 type change fro Func TaskLocal.withValue(_:operation:file:line:) has parameter 2 type change from Swift.String to (any _Concurrency.Actor)? Func TaskLocal.withValue(_:operation:file:line:) has parameter 3 type change from Swift.UInt to Swift.String +// Adopt typed throws in Task<> and Task::value +// (abi compat was handled but this test does not understand the silgen_name trickery) +Accessor Task.value.Get() has mangled name changing from 'Swift.Task.value.getter : A' to 'Swift.Task.__abi_value.getter : A' +Var Task.value has been renamed to Var __abi_value +Var Task.value has mangled name changing from 'Swift.Task.value : A' to 'Swift.Task.__abi_value : A' + // *** DO NOT DISABLE OR XFAIL THIS TEST. *** (See comment above.)