mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
The biggest annoyance here is having to clone all of the task creation functions for Embedded Swift because it can't use `any Actor` right now.
984 lines
39 KiB
Swift
984 lines
39 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
|
|
@_implementationOnly import _SwiftConcurrencyShims
|
|
|
|
// ==== 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, *)
|
|
@inlinable
|
|
@_unsafeInheritExecutor
|
|
public func withDiscardingTaskGroup<GroupResult>(
|
|
returning returnType: GroupResult.Type = GroupResult.self,
|
|
body: (inout DiscardingTaskGroup) async -> GroupResult
|
|
) async -> GroupResult {
|
|
#if compiler(>=5.5) && $BuiltinCreateTaskGroupWithFlags
|
|
let flags = taskGroupCreateFlags(
|
|
discardResults: true
|
|
)
|
|
|
|
let _group = Builtin.createTaskGroupWithFlags(flags, GroupResult.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
|
|
#else
|
|
fatalError("Swift compiler is incompatible with this SDK version")
|
|
#endif
|
|
}
|
|
|
|
/// 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)
|
|
}
|
|
|
|
// Clone the task-creation routines in Embedded Swift so that we don't
|
|
// introduce an implicit use of `any Actor`.
|
|
#if !$Embedded
|
|
|
|
/// Adds a child task to the group.
|
|
///
|
|
/// - Parameters:
|
|
/// - priority: The priority of the operation task.
|
|
/// Omit this parameter or pass `.unspecified`
|
|
/// to set the child task's priority to the priority of the group.
|
|
/// - operation: The operation to execute as part of the task group.
|
|
@_alwaysEmitIntoClient
|
|
@_allowFeatureSuppression(IsolatedAny)
|
|
#if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
|
|
@available(*, unavailable, message: "Unavailable in task-to-thread concurrency model", renamed: "addTask(operation:)")
|
|
#endif
|
|
public mutating func addTask(
|
|
priority: TaskPriority? = nil,
|
|
operation: __owned @Sendable @escaping @isolated(any) () async -> Void
|
|
) {
|
|
#if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
|
|
let flags = taskCreateFlags(
|
|
priority: priority, isChildTask: true, copyTaskLocals: false,
|
|
inheritContext: false, enqueueJob: false,
|
|
addPendingGroupTaskUnconditionally: true, isDiscardingTask: true
|
|
)
|
|
#else
|
|
let flags = taskCreateFlags(
|
|
priority: priority, isChildTask: true, copyTaskLocals: false,
|
|
inheritContext: false, enqueueJob: true,
|
|
addPendingGroupTaskUnconditionally: true, isDiscardingTask: true
|
|
)
|
|
#endif
|
|
|
|
// Create the task in this group.
|
|
#if $BuiltinCreateTask
|
|
let builtinSerialExecutor =
|
|
Builtin.extractFunctionIsolation(operation)?.unownedExecutor.executor
|
|
|
|
_ = Builtin.createDiscardingTask(flags: flags,
|
|
initialSerialExecutor: builtinSerialExecutor,
|
|
taskGroup: _group,
|
|
operation: operation)
|
|
#elseif $BuiltinCreateAsyncDiscardingTaskInGroup
|
|
_ = Builtin.createAsyncDiscardingTaskInGroup(flags, _group, operation)
|
|
#else
|
|
// This builtin happens to work, but the signature of the operation is
|
|
// incorrect, as the discarding group uses Void, and therefore has less
|
|
// generic parameters than the operation expected to be passed to
|
|
// createAsyncTaskInGroup. While this happened to work on some platforms,
|
|
// on others this causes issues, e.g. on wasm;
|
|
//
|
|
// Keep this branch for compatibility with old compilers, but use the
|
|
// correct 'createAsyncDiscardingTaskInGroup' when available (and a recent
|
|
// enough compiler is used).
|
|
_ = Builtin.createAsyncTaskInGroup(flags, _group, operation)
|
|
#endif
|
|
}
|
|
|
|
/// Adds a child task to the group, unless the group has been canceled.
|
|
///
|
|
/// - Parameters:
|
|
/// - priority: The priority of the operation task.
|
|
/// Omit this parameter or pass `.unspecified`
|
|
/// to set the child task's priority to the priority of the group.
|
|
/// - operation: The operation to execute as part of the task group.
|
|
/// - Returns: `true` if the child task was added to the group;
|
|
/// otherwise `false`.
|
|
@_alwaysEmitIntoClient
|
|
@_allowFeatureSuppression(IsolatedAny)
|
|
#if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
|
|
@available(*, unavailable, message: "Unavailable in task-to-thread concurrency model", renamed: "addTask(operation:)")
|
|
#endif
|
|
public mutating func addTaskUnlessCancelled(
|
|
priority: TaskPriority? = nil,
|
|
operation: __owned @Sendable @escaping @isolated(any) () async -> Void
|
|
) -> Bool {
|
|
let canAdd = _taskGroupAddPendingTask(group: _group, unconditionally: false)
|
|
|
|
guard canAdd else {
|
|
// the group is cancelled and is not accepting any new work
|
|
return false
|
|
}
|
|
#if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
|
|
let flags = taskCreateFlags(
|
|
priority: priority, isChildTask: true, copyTaskLocals: false,
|
|
inheritContext: false, enqueueJob: false,
|
|
addPendingGroupTaskUnconditionally: false, isDiscardingTask: true
|
|
)
|
|
#else
|
|
let flags = taskCreateFlags(
|
|
priority: priority, isChildTask: true, copyTaskLocals: false,
|
|
inheritContext: false, enqueueJob: true,
|
|
addPendingGroupTaskUnconditionally: false, isDiscardingTask: true
|
|
)
|
|
#endif
|
|
|
|
// Create the task in this group.
|
|
#if $BuiltinCreateTask
|
|
let builtinSerialExecutor =
|
|
Builtin.extractFunctionIsolation(operation)?.unownedExecutor.executor
|
|
|
|
_ = Builtin.createDiscardingTask(flags: flags,
|
|
initialSerialExecutor: builtinSerialExecutor,
|
|
taskGroup: _group,
|
|
operation: operation)
|
|
#elseif $BuiltinCreateAsyncDiscardingTaskInGroup
|
|
_ = Builtin.createAsyncDiscardingTaskInGroup(flags, _group, operation)
|
|
#else
|
|
// This builtin happens to work, but the signature of the operation is
|
|
// incorrect, as the discarding group uses Void, and therefore has less
|
|
// generic parameters than the operation expected to be passed to
|
|
// createAsyncTaskInGroup. While this happened to work on some platforms,
|
|
// on others this causes issues, e.g. on wasm;
|
|
//
|
|
// Keep this branch for compatibility with old compilers, but use the
|
|
// correct 'createAsyncDiscardingTaskInGroup' when available (and a recent
|
|
// enough compiler is used).
|
|
_ = Builtin.createAsyncTaskInGroup(flags, _group, operation)
|
|
#endif
|
|
|
|
return true
|
|
}
|
|
|
|
@_alwaysEmitIntoClient
|
|
@_allowFeatureSuppression(IsolatedAny)
|
|
public mutating func addTask(
|
|
operation: __owned @Sendable @escaping @isolated(any) () async -> Void
|
|
) {
|
|
let flags = taskCreateFlags(
|
|
priority: nil, isChildTask: true, copyTaskLocals: false,
|
|
inheritContext: false, enqueueJob: true,
|
|
addPendingGroupTaskUnconditionally: true, isDiscardingTask: true
|
|
)
|
|
|
|
// Create the task in this group.
|
|
#if $BuiltinCreateTask
|
|
let builtinSerialExecutor =
|
|
Builtin.extractFunctionIsolation(operation)?.unownedExecutor.executor
|
|
|
|
_ = Builtin.createDiscardingTask(flags: flags,
|
|
initialSerialExecutor: builtinSerialExecutor,
|
|
taskGroup: _group,
|
|
operation: operation)
|
|
#elseif $BuiltinCreateAsyncDiscardingTaskInGroup
|
|
_ = Builtin.createAsyncDiscardingTaskInGroup(flags, _group, operation)
|
|
#else
|
|
// This builtin happens to work, but the signature of the operation is
|
|
// incorrect, as the discarding group uses Void, and therefore has less
|
|
// generic parameters than the operation expected to be passed to
|
|
// createAsyncTaskInGroup. While this happened to work on some platforms,
|
|
// on others this causes issues, e.g. on wasm;
|
|
//
|
|
// Keep this branch for compatibility with old compilers, but use the
|
|
// correct 'createAsyncDiscardingTaskInGroup' when available (and a recent
|
|
// enough compiler is used).
|
|
_ = Builtin.createAsyncTaskInGroup(flags, _group, operation)
|
|
#endif
|
|
}
|
|
|
|
/// Adds a child task to the group, unless the group has been canceled.
|
|
///
|
|
/// - Parameters:
|
|
/// - operation: The operation to execute as part of the task group.
|
|
/// - Returns: `true` if the child task was added to the group;
|
|
/// otherwise `false`.
|
|
#if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
|
|
@available(*, unavailable, message: "Unavailable in task-to-thread concurrency model", renamed: "addTaskUnlessCancelled(operation:)")
|
|
#endif
|
|
@_allowFeatureSuppression(IsolatedAny)
|
|
@_alwaysEmitIntoClient
|
|
public mutating func addTaskUnlessCancelled(
|
|
operation: __owned @Sendable @escaping @isolated(any) () async -> Void
|
|
) -> Bool {
|
|
#if compiler(>=5.5) && $BuiltinCreateAsyncTaskInGroup
|
|
let canAdd = _taskGroupAddPendingTask(group: _group, unconditionally: false)
|
|
|
|
guard canAdd else {
|
|
// the group is cancelled and is not accepting any new work
|
|
return false
|
|
}
|
|
|
|
let flags = taskCreateFlags(
|
|
priority: nil, isChildTask: true, copyTaskLocals: false,
|
|
inheritContext: false, enqueueJob: true,
|
|
addPendingGroupTaskUnconditionally: false, isDiscardingTask: true
|
|
)
|
|
|
|
// Create the task in this group.
|
|
#if $BuiltinCreateTask
|
|
let builtinSerialExecutor =
|
|
Builtin.extractFunctionIsolation(operation)?.unownedExecutor.executor
|
|
|
|
_ = Builtin.createDiscardingTask(flags: flags,
|
|
initialSerialExecutor: builtinSerialExecutor,
|
|
taskGroup: _group,
|
|
operation: operation)
|
|
#elseif $BuiltinCreateAsyncDiscardingTaskInGroup
|
|
_ = Builtin.createAsyncDiscardingTaskInGroup(flags, _group, operation)
|
|
#else
|
|
// This builtin happens to work, but the signature of the operation is
|
|
// incorrect, as the discarding group uses Void, and therefore has less
|
|
// generic parameters than the operation expected to be passed to
|
|
// createAsyncTaskInGroup. While this happened to work on some platforms,
|
|
// on others this causes issues, e.g. on wasm;
|
|
//
|
|
// Keep this branch for compatibility with old compilers, but use the
|
|
// correct 'createAsyncDiscardingTaskInGroup' when available (and a recent
|
|
// enough compiler is used).
|
|
_ = Builtin.createAsyncTaskInGroup(flags, _group, operation)
|
|
#endif
|
|
|
|
return true
|
|
#else
|
|
fatalError("Unsupported Swift compiler")
|
|
#endif
|
|
}
|
|
|
|
// The Embedded clones of the task-creation routines.
|
|
#else
|
|
|
|
/// Adds a child task to the group.
|
|
///
|
|
/// - Parameters:
|
|
/// - priority: The priority of the operation task.
|
|
/// Omit this parameter or pass `.unspecified`
|
|
/// to set the child task's priority to the priority of the group.
|
|
/// - operation: The operation to execute as part of the task group.
|
|
@_alwaysEmitIntoClient
|
|
#if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
|
|
@available(*, unavailable, message: "Unavailable in task-to-thread concurrency model", renamed: "addTask(operation:)")
|
|
#endif
|
|
public mutating func addTask(
|
|
priority: TaskPriority? = nil,
|
|
operation: __owned @Sendable @escaping () async -> Void
|
|
) {
|
|
#if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
|
|
let flags = taskCreateFlags(
|
|
priority: priority, isChildTask: true, copyTaskLocals: false,
|
|
inheritContext: false, enqueueJob: false,
|
|
addPendingGroupTaskUnconditionally: true, isDiscardingTask: true
|
|
)
|
|
#else
|
|
let flags = taskCreateFlags(
|
|
priority: priority, isChildTask: true, copyTaskLocals: false,
|
|
inheritContext: false, enqueueJob: true,
|
|
addPendingGroupTaskUnconditionally: true, isDiscardingTask: true
|
|
)
|
|
#endif
|
|
|
|
// Create the task in this group.
|
|
#if $BuiltinCreateAsyncDiscardingTaskInGroup
|
|
_ = Builtin.createAsyncDiscardingTaskInGroup(flags, _group, operation)
|
|
#else
|
|
// This builtin happens to work, but the signature of the operation is
|
|
// incorrect, as the discarding group uses Void, and therefore has less
|
|
// generic parameters than the operation expected to be passed to
|
|
// createAsyncTaskInGroup. While this happened to work on some platforms,
|
|
// on others this causes issues, e.g. on wasm;
|
|
//
|
|
// Keep this branch for compatibility with old compilers, but use the
|
|
// correct 'createAsyncDiscardingTaskInGroup' when available (and a recent
|
|
// enough compiler is used).
|
|
_ = Builtin.createAsyncTaskInGroup(flags, _group, operation)
|
|
#endif
|
|
}
|
|
|
|
/// Adds a child task to the group, unless the group has been canceled.
|
|
///
|
|
/// - Parameters:
|
|
/// - priority: The priority of the operation task.
|
|
/// Omit this parameter or pass `.unspecified`
|
|
/// to set the child task's priority to the priority of the group.
|
|
/// - operation: The operation to execute as part of the task group.
|
|
/// - Returns: `true` if the child task was added to the group;
|
|
/// otherwise `false`.
|
|
@_alwaysEmitIntoClient
|
|
#if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
|
|
@available(*, unavailable, message: "Unavailable in task-to-thread concurrency model", renamed: "addTask(operation:)")
|
|
#endif
|
|
public mutating func addTaskUnlessCancelled(
|
|
priority: TaskPriority? = nil,
|
|
operation: __owned @Sendable @escaping () async -> Void
|
|
) -> Bool {
|
|
let canAdd = _taskGroupAddPendingTask(group: _group, unconditionally: false)
|
|
|
|
guard canAdd else {
|
|
// the group is cancelled and is not accepting any new work
|
|
return false
|
|
}
|
|
#if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
|
|
let flags = taskCreateFlags(
|
|
priority: priority, isChildTask: true, copyTaskLocals: false,
|
|
inheritContext: false, enqueueJob: false,
|
|
addPendingGroupTaskUnconditionally: false, isDiscardingTask: true
|
|
)
|
|
#else
|
|
let flags = taskCreateFlags(
|
|
priority: priority, isChildTask: true, copyTaskLocals: false,
|
|
inheritContext: false, enqueueJob: true,
|
|
addPendingGroupTaskUnconditionally: false, isDiscardingTask: true
|
|
)
|
|
#endif
|
|
|
|
// Create the task in this group.
|
|
#if $BuiltinCreateAsyncDiscardingTaskInGroup
|
|
_ = Builtin.createAsyncDiscardingTaskInGroup(flags, _group, operation)
|
|
#else
|
|
// This builtin happens to work, but the signature of the operation is
|
|
// incorrect, as the discarding group uses Void, and therefore has less
|
|
// generic parameters than the operation expected to be passed to
|
|
// createAsyncTaskInGroup. While this happened to work on some platforms,
|
|
// on others this causes issues, e.g. on wasm;
|
|
//
|
|
// Keep this branch for compatibility with old compilers, but use the
|
|
// correct 'createAsyncDiscardingTaskInGroup' when available (and a recent
|
|
// enough compiler is used).
|
|
_ = Builtin.createAsyncTaskInGroup(flags, _group, operation)
|
|
#endif
|
|
|
|
return true
|
|
}
|
|
|
|
@_alwaysEmitIntoClient
|
|
public mutating func addTask(
|
|
operation: __owned @Sendable @escaping () async -> Void
|
|
) {
|
|
let flags = taskCreateFlags(
|
|
priority: nil, isChildTask: true, copyTaskLocals: false,
|
|
inheritContext: false, enqueueJob: true,
|
|
addPendingGroupTaskUnconditionally: true, isDiscardingTask: true
|
|
)
|
|
|
|
// Create the task in this group.
|
|
#if $BuiltinCreateAsyncDiscardingTaskInGroup
|
|
_ = Builtin.createAsyncDiscardingTaskInGroup(flags, _group, operation)
|
|
#else
|
|
// This builtin happens to work, but the signature of the operation is
|
|
// incorrect, as the discarding group uses Void, and therefore has less
|
|
// generic parameters than the operation expected to be passed to
|
|
// createAsyncTaskInGroup. While this happened to work on some platforms,
|
|
// on others this causes issues, e.g. on wasm;
|
|
//
|
|
// Keep this branch for compatibility with old compilers, but use the
|
|
// correct 'createAsyncDiscardingTaskInGroup' when available (and a recent
|
|
// enough compiler is used).
|
|
_ = Builtin.createAsyncTaskInGroup(flags, _group, operation)
|
|
#endif
|
|
}
|
|
|
|
/// Adds a child task to the group, unless the group has been canceled.
|
|
///
|
|
/// - Parameters:
|
|
/// - operation: The operation to execute as part of the task group.
|
|
/// - Returns: `true` if the child task was added to the group;
|
|
/// otherwise `false`.
|
|
#if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
|
|
@available(*, unavailable, message: "Unavailable in task-to-thread concurrency model", renamed: "addTaskUnlessCancelled(operation:)")
|
|
#endif
|
|
@_alwaysEmitIntoClient
|
|
public mutating func addTaskUnlessCancelled(
|
|
operation: __owned @Sendable @escaping () async -> Void
|
|
) -> Bool {
|
|
#if compiler(>=5.5) && $BuiltinCreateAsyncTaskInGroup
|
|
let canAdd = _taskGroupAddPendingTask(group: _group, unconditionally: false)
|
|
|
|
guard canAdd else {
|
|
// the group is cancelled and is not accepting any new work
|
|
return false
|
|
}
|
|
|
|
let flags = taskCreateFlags(
|
|
priority: nil, isChildTask: true, copyTaskLocals: false,
|
|
inheritContext: false, enqueueJob: true,
|
|
addPendingGroupTaskUnconditionally: false, isDiscardingTask: true
|
|
)
|
|
|
|
// Create the task in this group.
|
|
#if $BuiltinCreateAsyncDiscardingTaskInGroup
|
|
_ = Builtin.createAsyncDiscardingTaskInGroup(flags, _group, operation)
|
|
#else
|
|
// This builtin happens to work, but the signature of the operation is
|
|
// incorrect, as the discarding group uses Void, and therefore has less
|
|
// generic parameters than the operation expected to be passed to
|
|
// createAsyncTaskInGroup. While this happened to work on some platforms,
|
|
// on others this causes issues, e.g. on wasm;
|
|
//
|
|
// Keep this branch for compatibility with old compilers, but use the
|
|
// correct 'createAsyncDiscardingTaskInGroup' when available (and a recent
|
|
// enough compiler is used).
|
|
_ = Builtin.createAsyncTaskInGroup(flags, _group, operation)
|
|
#endif
|
|
|
|
return true
|
|
#else
|
|
fatalError("Unsupported Swift compiler")
|
|
#endif
|
|
}
|
|
|
|
#endif // $Embedded
|
|
|
|
/// 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, *)
|
|
@inlinable
|
|
@_unsafeInheritExecutor
|
|
public func withThrowingDiscardingTaskGroup<GroupResult>(
|
|
returning returnType: GroupResult.Type = GroupResult.self,
|
|
body: (inout ThrowingDiscardingTaskGroup<Error>) async throws -> GroupResult
|
|
) async throws -> GroupResult {
|
|
#if compiler(>=5.5) && $BuiltinCreateTaskGroupWithFlags
|
|
let flags = taskGroupCreateFlags(
|
|
discardResults: true
|
|
)
|
|
|
|
let _group = Builtin.createTaskGroupWithFlags(flags, GroupResult.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
|
|
#else
|
|
fatalError("Swift compiler is incompatible with this SDK version")
|
|
#endif
|
|
}
|
|
|
|
|
|
/// 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)
|
|
}
|
|
|
|
#if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
|
|
@available(*, unavailable, message: "Unavailable in task-to-thread concurrency model", renamed: "addTask(operation:)")
|
|
#endif
|
|
@_alwaysEmitIntoClient
|
|
@_allowFeatureSuppression(IsolatedAny)
|
|
public mutating func addTask(
|
|
priority: TaskPriority? = nil,
|
|
operation: __owned @Sendable @escaping @isolated(any) () async throws -> Void
|
|
) {
|
|
#if compiler(>=5.5) && $BuiltinCreateAsyncTaskInGroup
|
|
let flags = taskCreateFlags(
|
|
priority: priority, isChildTask: true, copyTaskLocals: false,
|
|
inheritContext: false, enqueueJob: true,
|
|
addPendingGroupTaskUnconditionally: true, isDiscardingTask: true
|
|
)
|
|
|
|
// Create the task in this group.
|
|
#if $BuiltinCreateTask
|
|
let builtinSerialExecutor =
|
|
Builtin.extractFunctionIsolation(operation)?.unownedExecutor.executor
|
|
|
|
_ = Builtin.createDiscardingTask(flags: flags,
|
|
initialSerialExecutor: builtinSerialExecutor,
|
|
taskGroup: _group,
|
|
operation: operation)
|
|
#elseif $BuiltinCreateAsyncDiscardingTaskInGroup
|
|
_ = Builtin.createAsyncDiscardingTaskInGroup(flags, _group, operation)
|
|
#else
|
|
// This builtin happens to work, but the signature of the operation is
|
|
// incorrect, as the discarding group uses Void, and therefore has less
|
|
// generic parameters than the operation expected to be passed to
|
|
// createAsyncTaskInGroup. While this happened to work on some platforms,
|
|
// on others this causes issues, e.g. on wasm;
|
|
//
|
|
// Keep this branch for compatibility with old compilers, but use the
|
|
// correct 'createAsyncDiscardingTaskInGroup' when available (and a recent
|
|
// enough compiler is used).
|
|
_ = Builtin.createAsyncTaskInGroup(flags, _group, operation)
|
|
#endif
|
|
#else
|
|
fatalError("Unsupported Swift compiler")
|
|
#endif
|
|
}
|
|
|
|
#if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
|
|
@available(*, unavailable, message: "Unavailable in task-to-thread concurrency model", renamed: "addTask(operation:)")
|
|
#endif
|
|
@_alwaysEmitIntoClient
|
|
@_allowFeatureSuppression(IsolatedAny)
|
|
public mutating func addTaskUnlessCancelled(
|
|
priority: TaskPriority? = nil,
|
|
operation: __owned @Sendable @escaping @isolated(any) () async throws -> Void
|
|
) -> Bool {
|
|
#if compiler(>=5.5) && $BuiltinCreateAsyncTaskInGroup
|
|
let canAdd = _taskGroupAddPendingTask(group: _group, unconditionally: false)
|
|
|
|
guard canAdd else {
|
|
// the group is cancelled and is not accepting any new work
|
|
return false
|
|
}
|
|
|
|
let flags = taskCreateFlags(
|
|
priority: priority, isChildTask: true, copyTaskLocals: false,
|
|
inheritContext: false, enqueueJob: true,
|
|
addPendingGroupTaskUnconditionally: false, isDiscardingTask: true
|
|
)
|
|
|
|
// Create the task in this group.
|
|
#if $BuiltinCreateTask
|
|
let builtinSerialExecutor =
|
|
Builtin.extractFunctionIsolation(operation)?.unownedExecutor.executor
|
|
|
|
_ = Builtin.createDiscardingTask(flags: flags,
|
|
initialSerialExecutor: builtinSerialExecutor,
|
|
taskGroup: _group,
|
|
operation: operation)
|
|
#elseif $BuiltinCreateAsyncDiscardingTaskInGroup
|
|
_ = Builtin.createAsyncDiscardingTaskInGroup(flags, _group, operation)
|
|
#else
|
|
// This builtin happens to work, but the signature of the operation is
|
|
// incorrect, as the discarding group uses Void, and therefore has less
|
|
// generic parameters than the operation expected to be passed to
|
|
// createAsyncTaskInGroup. While this happened to work on some platforms,
|
|
// on others this causes issues, e.g. on wasm;
|
|
//
|
|
// Keep this branch for compatibility with old compilers, but use the
|
|
// correct 'createAsyncDiscardingTaskInGroup' when available (and a recent
|
|
// enough compiler is used).
|
|
_ = Builtin.createAsyncTaskInGroup(flags, _group, operation)
|
|
#endif
|
|
|
|
return true
|
|
#else
|
|
fatalError("Unsupported Swift compiler")
|
|
#endif
|
|
}
|
|
|
|
/// 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?
|