mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
statusCompletePendingReadyWaiting(), offer(), and poll() did a one-off compare_exchange_strong which could fail if the group was concurrently cancelled. Put these into loops so that they are retried when needed.
DiscardingTaskGroup creation passed the group result type as the task result type. waitAll() would then use the group result type when collecting task results. Since the task result type is always Void in this case, this would overflow the result buffer if the group result type was larger. This often works as it writes into the free space of the task allocator, but can crash if it happens to be at the end of a page or the group result type is particularly large.
rdar://151663730
(cherry picked from commit 5be22fa7cc)
542 lines
21 KiB
Swift
542 lines
21 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2020 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import Swift
|
|
|
|
// ==== DiscardingTaskGroup ---------------------------------------------------
|
|
|
|
/// Starts a new scope that can contain a dynamic number of child tasks.
|
|
///
|
|
/// Unlike a ``TaskGroup``, the child tasks as well as their results are
|
|
/// discarded as soon as the tasks complete. This prevents the discarding
|
|
/// task group from accumulating many results waiting to be consumed, and is
|
|
/// best applied in situations where the result of a child task is some form
|
|
/// of side-effect.
|
|
///
|
|
/// A group waits for all of its child tasks
|
|
/// to complete before it returns. Even cancelled tasks must run until
|
|
/// completion before this function returns.
|
|
/// Cancelled child tasks cooperatively react to cancellation and attempt
|
|
/// to return as early as possible.
|
|
/// After this function returns, the task group is always empty.
|
|
///
|
|
/// It is not possible to explicitly await completion of child-tasks,
|
|
/// however the group will automatically await *all* child task completions
|
|
/// before returning from this function:
|
|
///
|
|
/// ```
|
|
/// await withDiscardingTaskGroup(...) { group in
|
|
/// group.addTask { /* slow-task */ }
|
|
/// // slow-task executes...
|
|
/// }
|
|
/// // guaranteed that slow-task has completed and the group is empty & destroyed
|
|
/// ```
|
|
///
|
|
/// Task Group Cancellation
|
|
/// =======================
|
|
///
|
|
/// You can cancel a task group and all of its child tasks
|
|
/// by calling the ``TaskGroup/cancelAll()`` method on the task group,
|
|
/// or by canceling the task in which the group is running.
|
|
///
|
|
/// If you call `addTask(priority:operation:)` to create a new task in a canceled group,
|
|
/// that task is immediately canceled after creation.
|
|
/// Alternatively, you can call `asyncUnlessCancelled(priority:operation:)`,
|
|
/// which doesn't create the task if the group has already been canceled.
|
|
/// Choosing between these two functions
|
|
/// lets you control how to react to cancellation within a group:
|
|
/// some child tasks need to run regardless of cancellation,
|
|
/// but other tasks are better not even being created
|
|
/// when you know they can't produce useful results.
|
|
///
|
|
/// Because the tasks you add to a group with this method are nonthrowing,
|
|
/// those tasks can't respond to cancellation by throwing `CancellationError`.
|
|
/// The tasks must handle cancellation in some other way,
|
|
/// such as returning the work completed so far, returning an empty result, or returning `nil`.
|
|
/// For tasks that need to handle cancellation by throwing an error,
|
|
/// use the `withThrowingDiscardingTaskGroup(returning:body:)` method instead.
|
|
///
|
|
/// - SeeAlso: ``withThrowingDiscardingTaskGroup(returning:body:)``
|
|
@available(SwiftStdlib 5.9, *)
|
|
#if !hasFeature(Embedded)
|
|
@backDeployed(before: SwiftStdlib 6.0)
|
|
#endif
|
|
@inlinable
|
|
public func withDiscardingTaskGroup<GroupResult>(
|
|
returning returnType: GroupResult.Type = GroupResult.self,
|
|
isolation: isolated (any Actor)? = #isolation,
|
|
body: (inout DiscardingTaskGroup) async -> GroupResult
|
|
) async -> GroupResult {
|
|
let flags = taskGroupCreateFlags(
|
|
discardResults: true
|
|
)
|
|
|
|
let _group = Builtin.createTaskGroupWithFlags(flags, Void.self)
|
|
var group = DiscardingTaskGroup(group: _group)
|
|
defer { Builtin.destroyTaskGroup(_group) }
|
|
|
|
let result = await body(&group)
|
|
|
|
try! await group.awaitAllRemainingTasks() // try!-safe, cannot throw since this is a non throwing group
|
|
|
|
return result
|
|
}
|
|
|
|
// Note: hack to stage out @_unsafeInheritExecutor forms of various functions
|
|
// in favor of #isolation. The _unsafeInheritExecutor_ prefix is meaningful
|
|
// to the type checker.
|
|
//
|
|
// This function also doubles as an ABI-compatibility shim predating the
|
|
// introduction of #isolation.
|
|
@available(SwiftStdlib 5.9, *)
|
|
@_unsafeInheritExecutor // for ABI compatibility
|
|
@_silgen_name("$ss23withDiscardingTaskGroup9returning4bodyxxm_xs0bcD0VzYaXEtYalF")
|
|
public func _unsafeInheritExecutor_withDiscardingTaskGroup<GroupResult>(
|
|
returning returnType: GroupResult.Type = GroupResult.self,
|
|
body: (inout DiscardingTaskGroup) async -> GroupResult
|
|
) async -> GroupResult {
|
|
let flags = taskGroupCreateFlags(
|
|
discardResults: true
|
|
)
|
|
|
|
let _group = Builtin.createTaskGroupWithFlags(flags, Void.self)
|
|
var group = DiscardingTaskGroup(group: _group)
|
|
defer { Builtin.destroyTaskGroup(_group) }
|
|
|
|
let result = await body(&group)
|
|
|
|
try! await group.awaitAllRemainingTasks() // try!-safe, cannot throw since this is a non throwing group
|
|
|
|
return result
|
|
}
|
|
|
|
/// A discarding group that contains dynamically created child tasks.
|
|
///
|
|
/// To create a discarding task group,
|
|
/// call the ``withDiscardingTaskGroup(returning:body:)`` method.
|
|
///
|
|
/// Don't use a task group from outside the task where you created it.
|
|
/// In most cases,
|
|
/// the Swift type system prevents a task group from escaping like that
|
|
/// because adding a child task to a task group is a mutating operation,
|
|
/// and mutation operations can't be performed
|
|
/// from a concurrent execution context like a child task.
|
|
///
|
|
/// ### Task execution order
|
|
/// Tasks added to a task group execute concurrently, and may be scheduled in
|
|
/// any order.
|
|
///
|
|
/// ### Discarding behavior
|
|
/// A discarding task group eagerly discards and releases its child tasks as
|
|
/// soon as they complete. This allows for the efficient releasing of memory used
|
|
/// by those tasks, which are not retained for future `next()` calls, as would
|
|
/// be the case with a ``TaskGroup``.
|
|
///
|
|
/// ### Cancellation behavior
|
|
/// A discarding task group becomes cancelled in one of the following ways:
|
|
///
|
|
/// - when ``cancelAll()`` is invoked on it,
|
|
/// - when the ``Task`` running this task group is cancelled.
|
|
///
|
|
/// Since a `DiscardingTaskGroup` is a structured concurrency primitive, cancellation is
|
|
/// automatically propagated through all of its child-tasks (and their child
|
|
/// tasks).
|
|
///
|
|
/// A cancelled task group can still keep adding tasks, however they will start
|
|
/// being immediately cancelled, and may act accordingly to this. To avoid adding
|
|
/// new tasks to an already cancelled task group, use ``addTaskUnlessCancelled(priority:body:)``
|
|
/// rather than the plain ``addTask(priority:body:)`` which adds tasks unconditionally.
|
|
///
|
|
/// For information about the language-level concurrency model that `DiscardingTaskGroup` is part of,
|
|
/// see [Concurrency][concurrency] in [The Swift Programming Language][tspl].
|
|
///
|
|
/// [concurrency]: https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html
|
|
/// [tspl]: https://docs.swift.org/swift-book/
|
|
///
|
|
/// - SeeAlso: ``TaskGroup``
|
|
/// - SeeAlso: ``ThrowingTaskGroup``
|
|
/// - SeeAlso: ``ThrowingDiscardingTaskGroup``
|
|
@available(SwiftStdlib 5.9, *)
|
|
@frozen
|
|
public struct DiscardingTaskGroup {
|
|
|
|
@usableFromInline
|
|
internal let _group: Builtin.RawPointer
|
|
|
|
// No public initializers
|
|
@inlinable
|
|
init(group: Builtin.RawPointer) {
|
|
self._group = group
|
|
}
|
|
|
|
/// Await all the remaining tasks on this group.
|
|
///
|
|
/// - Throws: The first error that was encountered by this group.
|
|
@usableFromInline
|
|
internal mutating func awaitAllRemainingTasks() async throws {
|
|
let _: Void? = try await _taskGroupWaitAll(group: _group, bodyError: nil)
|
|
}
|
|
|
|
/// A Boolean value that indicates whether the group has any remaining tasks.
|
|
///
|
|
/// At the start of the body of a `withDiscardingTaskGroup(of:returning:body:)` call,
|
|
/// the task group is always empty.
|
|
///
|
|
/// It's guaranteed to be empty when returning from that body
|
|
/// because a task group waits for all child tasks to complete before returning.
|
|
///
|
|
/// - Returns: `true` if the group has no pending tasks; otherwise `false`.
|
|
public var isEmpty: Bool {
|
|
_taskGroupIsEmpty(_group)
|
|
}
|
|
|
|
/// Cancel all of the remaining tasks in the group.
|
|
///
|
|
/// If you add a task to a group after canceling the group,
|
|
/// that task is canceled immediately after being added to the group.
|
|
///
|
|
/// Immediately cancelled child tasks should therefore cooperatively check for and
|
|
/// react to cancellation, e.g. by throwing an `CancellationError` at their
|
|
/// earliest convenience, or otherwise handling the cancellation.
|
|
///
|
|
/// There are no restrictions on where you can call this method.
|
|
/// Code inside a child task or even another task can cancel a group,
|
|
/// however one should be very careful to not keep a reference to the
|
|
/// group longer than the `with...TaskGroup(...) { ... }` method body is executing.
|
|
///
|
|
/// - SeeAlso: `Task.isCancelled`
|
|
/// - SeeAlso: `DiscardingTaskGroup.isCancelled`
|
|
public func cancelAll() {
|
|
_taskGroupCancelAll(group: _group)
|
|
}
|
|
|
|
/// A Boolean value that indicates whether the group was canceled.
|
|
///
|
|
/// To cancel a group, call the `DiscardingTaskGroup.cancelAll()` method.
|
|
///
|
|
/// If the task that's currently running this group is canceled,
|
|
/// the group is also implicitly canceled,
|
|
/// which is also reflected in this property's value.
|
|
public var isCancelled: Bool {
|
|
return _taskGroupIsCancelled(group: _group)
|
|
}
|
|
}
|
|
|
|
@available(SwiftStdlib 5.9, *)
|
|
@available(*, unavailable)
|
|
extension DiscardingTaskGroup: Sendable { }
|
|
|
|
// ==== ThrowingDiscardingTaskGroup -------------------------------------------
|
|
|
|
/// Starts a new scope that can contain a dynamic number of child tasks.
|
|
///
|
|
/// Unlike a ``ThrowingTaskGroup``, the child tasks as well as their results are
|
|
/// discarded as soon as the tasks complete. This prevents the discarding
|
|
/// task group from accumulating many results waiting to be consumed, and is
|
|
/// best applied in situations where the result of a child task is some form
|
|
/// of side-effect.
|
|
///
|
|
/// A group waits for all of its child tasks
|
|
/// to complete before it returns. Even cancelled tasks must run until
|
|
/// completion before this function returns.
|
|
/// Cancelled child tasks cooperatively react to cancellation and attempt
|
|
/// to return as early as possible.
|
|
/// After this function returns, the task group is always empty.
|
|
///
|
|
/// It is not possible to explicitly await completion of child-tasks,
|
|
/// however the group will automatically await *all* child task completions
|
|
/// before returning from this function:
|
|
///
|
|
/// ```
|
|
/// try await withThrowingDiscardingTaskGroup(of: Void.self) { group in
|
|
/// group.addTask { /* slow-task */ }
|
|
/// // slow-task executes...
|
|
/// }
|
|
/// // guaranteed that slow-task has completed and the group is empty & destroyed
|
|
/// ```
|
|
///
|
|
/// Task Group Cancellation
|
|
/// =======================
|
|
///
|
|
/// You can cancel a task group and all of its child tasks
|
|
/// by calling the ``TaskGroup/cancelAll()`` method on the task group,
|
|
/// or by canceling the task in which the group is running.
|
|
///
|
|
/// If you call `addTask(priority:operation:)` to create a new task in a canceled group,
|
|
/// that task is immediately canceled after creation.
|
|
/// Alternatively, you can call `asyncUnlessCancelled(priority:operation:)`,
|
|
/// which doesn't create the task if the group has already been canceled.
|
|
/// Choosing between these two functions
|
|
/// lets you control how to react to cancellation within a group:
|
|
/// some child tasks need to run regardless of cancellation,
|
|
/// but other tasks are better not even being created
|
|
/// when you know they can't produce useful results.
|
|
///
|
|
/// Error Handling and Implicit Cancellation
|
|
/// ========================================
|
|
///
|
|
/// Since it is not possible to explicitly await individual task completions,
|
|
/// it is also not possible to "re-throw" an error thrown by one of the child
|
|
/// tasks using the same pattern as one would in a ``ThrowingTaskGroup``:
|
|
///
|
|
/// ```
|
|
/// // ThrowingTaskGroup, pattern not applicable to ThrowingDiscardingTaskGroup
|
|
/// try await withThrowingTaskGroup(of: Void.self) { group in
|
|
/// group.addTask { try boom() }
|
|
/// try await group.next() // re-throws "boom"
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// Since discarding task groups don't have access to `next()`, this pattern
|
|
/// cannot be used.
|
|
/// Instead,
|
|
/// a *throwing discarding task group implicitly cancels itself whenever any
|
|
/// of its child tasks throws*.
|
|
///
|
|
/// The *first error* thrown inside such task group
|
|
/// is then retained and thrown
|
|
/// out of the `withThrowingDiscardingTaskGroup` method when it returns.
|
|
///
|
|
/// ```
|
|
/// try await withThrowingDiscardingTaskGroup { group in
|
|
/// group.addTask { try boom(1) }
|
|
/// group.addTask { try boom(2, after: .seconds(5)) }
|
|
/// group.addTask { try boom(3, after: .seconds(5)) }
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// Generally, this suits the typical use cases of a
|
|
/// discarding task group well, however, if you want to prevent specific
|
|
/// errors from canceling the group you can catch them inside the child
|
|
/// task's body like this:
|
|
///
|
|
/// ```
|
|
/// try await withThrowingDiscardingTaskGroup { group in
|
|
/// group.addTask {
|
|
/// do {
|
|
/// try boom(1)
|
|
/// } catch is HarmlessError {
|
|
/// return
|
|
/// }
|
|
/// }
|
|
/// group.addTask {
|
|
/// try boom(2, after: .seconds(5))
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
@available(SwiftStdlib 5.9, *)
|
|
#if !hasFeature(Embedded)
|
|
@backDeployed(before: SwiftStdlib 6.0)
|
|
#endif
|
|
@inlinable
|
|
public func withThrowingDiscardingTaskGroup<GroupResult>(
|
|
returning returnType: GroupResult.Type = GroupResult.self,
|
|
isolation: isolated (any Actor)? = #isolation,
|
|
body: (inout ThrowingDiscardingTaskGroup<Error>) async throws -> GroupResult
|
|
) async throws -> GroupResult {
|
|
let flags = taskGroupCreateFlags(
|
|
discardResults: true
|
|
)
|
|
|
|
let _group = Builtin.createTaskGroupWithFlags(flags, Void.self)
|
|
var group = ThrowingDiscardingTaskGroup<Error>(group: _group)
|
|
defer { Builtin.destroyTaskGroup(_group) }
|
|
|
|
let result: GroupResult
|
|
do {
|
|
result = try await body(&group)
|
|
} catch {
|
|
group.cancelAll()
|
|
|
|
try await group.awaitAllRemainingTasks(bodyError: error)
|
|
|
|
throw error
|
|
}
|
|
|
|
try await group.awaitAllRemainingTasks(bodyError: nil)
|
|
|
|
return result
|
|
}
|
|
|
|
@available(SwiftStdlib 5.9, *)
|
|
@_unsafeInheritExecutor // for ABI compatibility
|
|
@_silgen_name("$ss31withThrowingDiscardingTaskGroup9returning4bodyxxm_xs0bcdE0Vys5Error_pGzYaKXEtYaKlF")
|
|
public func _unsafeInheritExecutor_withThrowingDiscardingTaskGroup<GroupResult>(
|
|
returning returnType: GroupResult.Type = GroupResult.self,
|
|
body: (inout ThrowingDiscardingTaskGroup<Error>) async throws -> GroupResult
|
|
) async throws -> GroupResult {
|
|
let flags = taskGroupCreateFlags(
|
|
discardResults: true
|
|
)
|
|
|
|
let _group = Builtin.createTaskGroupWithFlags(flags, Void.self)
|
|
var group = ThrowingDiscardingTaskGroup<Error>(group: _group)
|
|
defer { Builtin.destroyTaskGroup(_group) }
|
|
|
|
let result: GroupResult
|
|
do {
|
|
result = try await body(&group)
|
|
} catch {
|
|
group.cancelAll()
|
|
|
|
try await group.awaitAllRemainingTasks(bodyError: error)
|
|
|
|
throw error
|
|
}
|
|
|
|
try await group.awaitAllRemainingTasks(bodyError: nil)
|
|
|
|
return result
|
|
}
|
|
|
|
|
|
/// A throwing discarding group that contains dynamically created child tasks.
|
|
///
|
|
/// To create a discarding task group,
|
|
/// call the ``withDiscardingTaskGroup(returning:body:)`` method.
|
|
///
|
|
/// Don't use a task group from outside the task where you created it.
|
|
/// In most cases,
|
|
/// the Swift type system prevents a task group from escaping like that
|
|
/// because adding a child task to a task group is a mutating operation,
|
|
/// and mutation operations can't be performed
|
|
/// from a concurrent execution context like a child task.
|
|
///
|
|
/// ### Task execution order
|
|
/// Tasks added to a task group execute concurrently, and may be scheduled in
|
|
/// any order.
|
|
///
|
|
/// ### Discarding behavior
|
|
/// A discarding task group eagerly discards and releases its child tasks as
|
|
/// soon as they complete. This allows for the efficient releasing of memory used
|
|
/// by those tasks, which are not retained for future `next()` calls, as would
|
|
/// be the case with a ``TaskGroup``.
|
|
///
|
|
/// ### Cancellation behavior
|
|
/// A throwing discarding task group becomes cancelled in one of the following ways:
|
|
///
|
|
/// - when ``cancelAll()`` is invoked on it,
|
|
/// - when an error is thrown out of the `withThrowingDiscardingTaskGroup { ... }` closure,
|
|
/// - when the ``Task`` running this task group is cancelled.
|
|
///
|
|
/// But also, and uniquely in *discarding* task groups:
|
|
/// - when *any* of its child tasks throws.
|
|
///
|
|
/// The group becoming cancelled automatically, and cancelling all of its child tasks,
|
|
/// whenever *any* child task throws an error is a behavior unique to discarding task groups,
|
|
/// because achieving such semantics is not possible otherwise, due to the missing `next()` method
|
|
/// on discarding groups. Accumulating task groups can implement this by manually polling `next()`
|
|
/// and deciding to `cancelAll()` when they decide an error should cause the group to become cancelled,
|
|
/// however a discarding group cannot poll child tasks for results and therefore assumes that child
|
|
/// task throws are an indication of a group wide failure. In order to avoid such behavior,
|
|
/// use a ``DiscardingTaskGroup`` instead of a throwing one, or catch specific errors in
|
|
/// operations submitted using `addTask`.
|
|
///
|
|
/// Since a `ThrowingDiscardingTaskGroup` is a structured concurrency primitive, cancellation is
|
|
/// automatically propagated through all of its child-tasks (and their child
|
|
/// tasks).
|
|
///
|
|
/// A cancelled task group can still keep adding tasks, however they will start
|
|
/// being immediately cancelled, and may act accordingly to this. To avoid adding
|
|
/// new tasks to an already cancelled task group, use ``addTaskUnlessCancelled(priority:body:)``
|
|
/// rather than the plain ``addTask(priority:body:)`` which adds tasks unconditionally.
|
|
///
|
|
/// For information about the language-level concurrency model that `DiscardingTaskGroup` is part of,
|
|
/// see [Concurrency][concurrency] in [The Swift Programming Language][tspl].
|
|
///
|
|
/// [concurrency]: https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html
|
|
/// [tspl]: https://docs.swift.org/swift-book/
|
|
///
|
|
/// - SeeAlso: ``TaskGroup``
|
|
/// - SeeAlso: ``ThrowingTaskGroup``
|
|
/// - SeeAlso: ``DiscardingTaskGroup``
|
|
@available(SwiftStdlib 5.9, *)
|
|
@frozen
|
|
public struct ThrowingDiscardingTaskGroup<Failure: Error> {
|
|
|
|
@usableFromInline
|
|
internal let _group: Builtin.RawPointer
|
|
|
|
// No public initializers
|
|
@inlinable
|
|
init(group: Builtin.RawPointer) {
|
|
self._group = group
|
|
}
|
|
|
|
/// Await all the remaining tasks on this group.
|
|
@usableFromInline
|
|
internal mutating func awaitAllRemainingTasks(bodyError: Error?) async throws {
|
|
let _: Void? = try await _taskGroupWaitAll(group: _group, bodyError: bodyError)
|
|
}
|
|
|
|
/// A Boolean value that indicates whether the group has any remaining tasks.
|
|
///
|
|
/// At the start of the body of a `withThrowingDiscardingTaskGroup(returning:body:)` call,
|
|
/// the task group is always empty.
|
|
///
|
|
/// It's guaranteed to be empty when returning from that body
|
|
/// because a task group waits for all child tasks to complete before returning.
|
|
///
|
|
/// - Returns: `true` if the group has no pending tasks; otherwise `false`.
|
|
public var isEmpty: Bool {
|
|
_taskGroupIsEmpty(_group)
|
|
}
|
|
|
|
/// Cancel all of the remaining tasks in the group.
|
|
///
|
|
/// If you add a task to a group after canceling the group,
|
|
/// that task is canceled immediately after being added to the group.
|
|
///
|
|
/// Immediately cancelled child tasks should therefore cooperatively check for and
|
|
/// react to cancellation, e.g. by throwing an `CancellationError` at their
|
|
/// earliest convenience, or otherwise handling the cancellation.
|
|
///
|
|
/// There are no restrictions on where you can call this method.
|
|
/// Code inside a child task or even another task can cancel a group,
|
|
/// however one should be very careful to not keep a reference to the
|
|
/// group longer than the `with...TaskGroup(...) { ... }` method body is executing.
|
|
///
|
|
/// - SeeAlso: `Task.isCancelled`
|
|
/// - SeeAlso: `ThrowingDiscardingTaskGroup.isCancelled`
|
|
public func cancelAll() {
|
|
_taskGroupCancelAll(group: _group)
|
|
}
|
|
|
|
/// A Boolean value that indicates whether the group was canceled.
|
|
///
|
|
/// To cancel a group, call the `ThrowingDiscardingTaskGroup.cancelAll()` method.
|
|
///
|
|
/// If the task that's currently running this group is canceled,
|
|
/// the group is also implicitly canceled,
|
|
/// which is also reflected in this property's value.
|
|
public var isCancelled: Bool {
|
|
return _taskGroupIsCancelled(group: _group)
|
|
}
|
|
}
|
|
|
|
@available(SwiftStdlib 5.9, *)
|
|
@available(*, unavailable)
|
|
extension ThrowingDiscardingTaskGroup: Sendable { }
|
|
|
|
// ==== -----------------------------------------------------------------------
|
|
// MARK: Runtime functions
|
|
|
|
/// Always returns `nil`.
|
|
@available(SwiftStdlib 5.9, *)
|
|
@usableFromInline
|
|
@discardableResult
|
|
@_silgen_name("swift_taskGroup_waitAll")
|
|
func _taskGroupWaitAll<T>(
|
|
group: Builtin.RawPointer,
|
|
bodyError: Error?
|
|
) async throws -> T?
|