mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
This is the original spelling which was not accepted. We kept it for a bit to give adopters time to switch but it's time to remove it now.
436 lines
14 KiB
Swift
436 lines
14 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2020-2025 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 SwiftConcurrencyInternalShims
|
|
|
|
// ==== Task.startSynchronously ------------------------------------------------
|
|
|
|
% METHOD_VARIANTS = [
|
|
% 'THROWING',
|
|
% 'NON_THROWING',
|
|
% ]
|
|
% for THROWING_VARIANT in METHOD_VARIANTS:
|
|
|
|
% IS_THROWING = THROWING_VARIANT == 'THROWING'
|
|
% if IS_THROWING:
|
|
% FAILURE_TYPE = 'Error'
|
|
% THROWS = 'throws '
|
|
% else:
|
|
% FAILURE_TYPE = 'Never'
|
|
% THROWS = ''
|
|
% end
|
|
|
|
% end
|
|
|
|
// ==== Task.immediate(Detached) ---------------------------------------------------------
|
|
|
|
% METHOD_VARIANTS = [
|
|
% ('immediate', 'THROWING'),
|
|
% ('immediate', 'NON_THROWING'),
|
|
% ('immediateDetached', 'THROWING'),
|
|
% ('immediateDetached', 'NON_THROWING'),
|
|
% ]
|
|
% for (METHOD_NAME, THROWING_VARIANT) in METHOD_VARIANTS:
|
|
|
|
% IS_THROWING = THROWING_VARIANT == 'THROWING'
|
|
% IS_DETACHED = 'Detached' in METHOD_NAME
|
|
% if IS_THROWING:
|
|
% FAILURE_TYPE = 'Error'
|
|
% THROWS = 'throws '
|
|
% else:
|
|
% FAILURE_TYPE = 'Never'
|
|
% THROWS = ''
|
|
% end
|
|
|
|
@available(SwiftStdlib 6.2, *)
|
|
extension Task where Failure == ${FAILURE_TYPE} {
|
|
|
|
% if IS_DETACHED:
|
|
/// Create and immediately start running a new task in the context of the calling thread/task.
|
|
% else:
|
|
/// Create and immediately start running a new detached task in the context of the calling thread/task.
|
|
% end # IS_DETACHED
|
|
///
|
|
/// This function _starts_ the created task on the calling context.
|
|
/// The task will continue executing on the caller's context until it suspends,
|
|
/// and after suspension will resume on the adequate executor. For a nonisolated
|
|
/// operation this means running on the global concurrent pool, and on an isolated
|
|
/// operation it means the appropriate executor of that isolation context.
|
|
///
|
|
/// As indicated by the lack of `async` on this method, this method does _not_
|
|
/// suspend, and instead takes over the calling task's (thread's) execution in
|
|
/// a synchronous manner.
|
|
///
|
|
/// Other than the execution semantics discussed above, the created task
|
|
/// is semantically equivalent to a task created using
|
|
% if IS_DETACHED:
|
|
/// the ``Task/detached`` function.
|
|
% else:
|
|
/// the ``Task/init`` initializer.
|
|
% end
|
|
///
|
|
/// - Parameters:
|
|
/// - name: The high-level human-readable name given for this task
|
|
/// - priority: The priority of the task.
|
|
/// Pass `nil` to use the ``Task/basePriority`` of the current task (if there is one).
|
|
/// - taskExecutor: The task executor that the child task should be started on and keep using.
|
|
/// Explicitly passing `nil` as the executor preference is equivalent to no preference,
|
|
/// and effectively means to inherit the outer context's executor preference.
|
|
/// You can also pass the ``globalConcurrentExecutor`` global executor explicitly.
|
|
/// - operation: the operation to be run immediately upon entering the task.
|
|
/// - Returns: A reference to the unstructured task which may be awaited on.
|
|
@available(SwiftStdlib 6.2, *)
|
|
@_alwaysEmitIntoClient
|
|
@discardableResult
|
|
public static func ${METHOD_NAME}(
|
|
name: String? = nil,
|
|
priority: TaskPriority? = nil,
|
|
executorPreference taskExecutor: consuming (any TaskExecutor)? = nil,
|
|
@_implicitSelfCapture @_inheritActorContext(always) operation: sending @isolated(any) @escaping () async ${THROWS} -> Success
|
|
) -> Task<Success, ${FAILURE_TYPE}> {
|
|
|
|
let builtinSerialExecutor =
|
|
unsafe Builtin.extractFunctionIsolation(operation)?.unownedExecutor.executor
|
|
|
|
// Determine if we're switching isolation dynamically.
|
|
// If not, we can run the task synchronously and therefore MUST NOT "enqueue" it.
|
|
let flagsMustNotCrash: UInt64 = 0
|
|
let canRunSynchronously: Bool =
|
|
if let builtinSerialExecutor {
|
|
_taskIsCurrentExecutor(executor: builtinSerialExecutor, flags: flagsMustNotCrash)
|
|
} else {
|
|
true // if there is no target executor, we can run synchronously
|
|
}
|
|
|
|
let flags = taskCreateFlags(
|
|
priority: priority,
|
|
isChildTask: false,
|
|
copyTaskLocals: ${'true' if not IS_DETACHED else 'false /* detached */'},
|
|
inheritContext: ${'true' if not IS_DETACHED else 'false /* detached */'},
|
|
enqueueJob: !canRunSynchronously,
|
|
addPendingGroupTaskUnconditionally: false,
|
|
isDiscardingTask: false,
|
|
isSynchronousStart: true
|
|
)
|
|
|
|
var task: Builtin.NativeObject?
|
|
#if $BuiltinCreateAsyncTaskName
|
|
if let name {
|
|
#if $BuiltinCreateAsyncTaskOwnedTaskExecutor
|
|
task =
|
|
unsafe name.utf8CString.withUnsafeBufferPointer { nameBytes in
|
|
Builtin.createTask(
|
|
flags: flags,
|
|
initialSerialExecutor: builtinSerialExecutor,
|
|
initialTaskExecutorConsuming: taskExecutor,
|
|
taskName: nameBytes.baseAddress!._rawValue,
|
|
operation: operation).0
|
|
}
|
|
#else // no $BuiltinCreateAsyncTaskOwnedTaskExecutor
|
|
task =
|
|
unsafe name.utf8CString.withUnsafeBufferPointer { nameBytes in
|
|
Builtin.createTask(
|
|
flags: flags,
|
|
initialSerialExecutor: builtinSerialExecutor,
|
|
taskName: nameBytes.baseAddress!._rawValue,
|
|
operation: operation).0
|
|
}
|
|
#endif // $BuiltinCreateAsyncTaskOwnedTaskExecutor
|
|
} // let name
|
|
#endif // $BuiltinCreateAsyncTaskName
|
|
|
|
// Task name was not set, or task name createTask is unavailable
|
|
if task == nil {
|
|
assert(name == nil)
|
|
#if $BuiltinCreateAsyncTaskOwnedTaskExecutor
|
|
task = Builtin.createTask(
|
|
flags: flags,
|
|
initialSerialExecutor: builtinSerialExecutor,
|
|
initialTaskExecutorConsuming: taskExecutor,
|
|
operation: operation).0
|
|
#else
|
|
// legacy branch for the non-consuming task executor
|
|
let executorBuiltin: Builtin.Executor =
|
|
taskExecutor.asUnownedTaskExecutor().executor
|
|
|
|
task = Builtin.createTask(
|
|
flags: flags,
|
|
initialSerialExecutor: builtinSerialExecutor,
|
|
initialTaskExecutor: executorBuiltin,
|
|
operation: operation).0
|
|
#endif
|
|
}
|
|
|
|
if task == nil {
|
|
// either no task name was set, or names are unsupported
|
|
task = Builtin.createTask(
|
|
flags: flags,
|
|
initialSerialExecutor: builtinSerialExecutor,
|
|
operation: operation).0
|
|
}
|
|
|
|
if canRunSynchronously {
|
|
_startTaskImmediately(task!, targetExecutor: builtinSerialExecutor)
|
|
}
|
|
return Task<Success, ${FAILURE_TYPE}>(task!)
|
|
}
|
|
}
|
|
%end
|
|
|
|
%{
|
|
GROUP_AND_OP_INFO = [
|
|
(
|
|
'TaskGroup',
|
|
[
|
|
'addImmediateTask',
|
|
'addImmediateTaskUnlessCancelled'
|
|
],
|
|
'',
|
|
'ChildTaskResult'
|
|
),
|
|
(
|
|
'ThrowingTaskGroup',
|
|
[
|
|
'addImmediateTask',
|
|
'addImmediateTaskUnlessCancelled'
|
|
],
|
|
'throws ',
|
|
'ChildTaskResult'
|
|
),
|
|
(
|
|
'DiscardingTaskGroup',
|
|
[
|
|
'addImmediateTask',
|
|
'addImmediateTaskUnlessCancelled'
|
|
],
|
|
'',
|
|
'Void'
|
|
),
|
|
(
|
|
'ThrowingDiscardingTaskGroup',
|
|
[
|
|
'addImmediateTask',
|
|
'addImmediateTaskUnlessCancelled'
|
|
],
|
|
'throws ',
|
|
'Void'
|
|
),
|
|
]
|
|
}%
|
|
% for (GROUP_TYPE, METHOD_NAMES, THROWS, RESULT_TYPE) in GROUP_AND_OP_INFO:
|
|
% for METHOD_NAME in METHOD_NAMES:
|
|
%
|
|
% IS_DISCARDING = 'Discarding' in GROUP_TYPE
|
|
% IS_ADD_UNLESS_CANCELLED = METHOD_NAME == "addImmediateTaskUnlessCancelled"
|
|
%
|
|
% ARROW_RETURN_TYPE = "-> Bool " if IS_ADD_UNLESS_CANCELLED else ""
|
|
%
|
|
% if IS_DISCARDING:
|
|
% TASK_CREATE_FN = 'Builtin.createDiscardingTask'
|
|
% else:
|
|
% TASK_CREATE_FN = 'Builtin.createTask'
|
|
% end
|
|
|
|
@available(SwiftStdlib 6.2, *)
|
|
extension ${GROUP_TYPE} {
|
|
|
|
/// Add a child task to the group and immediately start running it in the context of the calling thread/task.
|
|
///
|
|
/// This function _starts_ the created task on the calling context.
|
|
/// The task will continue executing on the caller's context until it suspends,
|
|
/// and after suspension will resume on the adequate executor. For a nonisolated
|
|
/// operation this means running on the global concurrent pool, and on an isolated
|
|
/// operation it means the appropriate executor of that isolation context.
|
|
///
|
|
/// As indicated by the lack of `async` on this method, this method does _not_
|
|
/// suspend, and instead takes over the calling task's (thread's) execution in
|
|
/// a synchronous manner.
|
|
///
|
|
/// Other than the execution semantics discussed above, the created task
|
|
/// is semantically equivalent to its basic version which can be
|
|
/// created using ``${GROUP_TYPE}/addTask``.
|
|
///
|
|
/// - Parameters:
|
|
/// - name: Human readable name of this task.
|
|
/// - priority: The priority of the operation task.
|
|
/// Omit this parameter or pass `nil` to inherit the task group's base priority.
|
|
/// - taskExecutor: The task executor that the child task should be started on and keep using.
|
|
/// Explicitly passing `nil` as the executor preference is equivalent to
|
|
/// calling the `${METHOD_NAME}` method without a preference, and effectively
|
|
/// means to inherit the outer context's executor preference.
|
|
/// You can also pass the ``globalConcurrentExecutor`` global executor explicitly.
|
|
/// - operation: The operation to execute as part of the task group.
|
|
% if IS_ADD_UNLESS_CANCELLED:
|
|
/// - Returns: `true` if the child task was added to the group;
|
|
/// otherwise `false`.
|
|
% end
|
|
@available(SwiftStdlib 6.2, *)
|
|
@_alwaysEmitIntoClient
|
|
public mutating func ${METHOD_NAME}( // in ${GROUP_TYPE}
|
|
name: String? = nil,
|
|
priority: TaskPriority? = nil,
|
|
executorPreference taskExecutor: consuming (any TaskExecutor)? = nil,
|
|
@_inheritActorContext @_implicitSelfCapture operation: sending @isolated(any) @escaping () async ${THROWS}-> ${RESULT_TYPE}
|
|
) ${ARROW_RETURN_TYPE}{
|
|
|
|
% if IS_ADD_UNLESS_CANCELLED:
|
|
let canAdd = _taskGroupAddPendingTask(group: _group, unconditionally: false)
|
|
|
|
guard canAdd else {
|
|
// the group is cancelled and is not accepting any new work
|
|
return false
|
|
}
|
|
% end # IS_ADD_UNLESS_CANCELLED
|
|
|
|
let flags = taskCreateFlags(
|
|
priority: priority,
|
|
isChildTask: true,
|
|
copyTaskLocals: false,
|
|
inheritContext: false,
|
|
enqueueJob: false, // don't enqueue, we'll run it manually
|
|
% if IS_ADD_UNLESS_CANCELLED:
|
|
% # In this case, we already added the pending task count before we create the task
|
|
% # so we must not add to the pending counter again.
|
|
addPendingGroupTaskUnconditionally: false,
|
|
% else:
|
|
addPendingGroupTaskUnconditionally: true,
|
|
% end
|
|
isDiscardingTask: ${str(IS_DISCARDING).lower()},
|
|
isSynchronousStart: true
|
|
)
|
|
|
|
let builtinSerialExecutor =
|
|
unsafe Builtin.extractFunctionIsolation(operation)?.unownedExecutor.executor
|
|
|
|
var task: Builtin.NativeObject?
|
|
|
|
#if $BuiltinCreateAsyncTaskName
|
|
if let name {
|
|
task =
|
|
unsafe name.utf8CString.withUnsafeBufferPointer { nameBytes in
|
|
${TASK_CREATE_FN}(
|
|
flags: flags,
|
|
initialSerialExecutor: builtinSerialExecutor,
|
|
taskGroup: _group,
|
|
initialTaskExecutorConsuming: taskExecutor,
|
|
taskName: nameBytes.baseAddress!._rawValue,
|
|
operation: operation).0
|
|
}
|
|
}
|
|
#endif // $BuiltinCreateAsyncTaskName
|
|
|
|
// Task name was not set, or task name createTask is unavailable
|
|
if task == nil, let taskExecutor {
|
|
#if $BuiltinCreateAsyncTaskOwnedTaskExecutor
|
|
task = ${TASK_CREATE_FN}(
|
|
flags: flags,
|
|
initialSerialExecutor: builtinSerialExecutor,
|
|
taskGroup: _group,
|
|
initialTaskExecutorConsuming: taskExecutor,
|
|
operation: operation).0
|
|
#else
|
|
// legacy branch for the non-consuming task executor
|
|
let executorBuiltin: Builtin.Executor =
|
|
taskExecutor.asUnownedTaskExecutor().executor
|
|
|
|
task = ${TASK_CREATE_FN}(
|
|
flags: flags,
|
|
initialSerialExecutor: builtinSerialExecutor,
|
|
taskGroup: _group,
|
|
initialTaskExecutor: executorBuiltin,
|
|
operation: operation).0
|
|
#endif
|
|
}
|
|
|
|
if task == nil {
|
|
task = ${TASK_CREATE_FN}(
|
|
flags: flags,
|
|
initialSerialExecutor: builtinSerialExecutor,
|
|
taskGroup: _group,
|
|
operation: operation).0
|
|
}
|
|
|
|
// Assert that we did create the task, but there's no need to store it,
|
|
// as it was added to the group itself.
|
|
assert(task != nil, "Expected task to be created!")
|
|
|
|
_startTaskImmediately(task!, targetExecutor: builtinSerialExecutor)
|
|
|
|
% if IS_ADD_UNLESS_CANCELLED:
|
|
return true // task successfully enqueued
|
|
% end
|
|
}
|
|
}
|
|
% end # METHOD_NAMES
|
|
%end # GROUP_TYPES
|
|
|
|
// ==== Legacy SPI -------------------------------------------------------------
|
|
|
|
% METHOD_VARIANTS = [
|
|
% 'THROWING',
|
|
% 'NON_THROWING',
|
|
% ]
|
|
% for THROWING_VARIANT in METHOD_VARIANTS:
|
|
|
|
% IS_THROWING = THROWING_VARIANT == 'THROWING'
|
|
% if IS_THROWING:
|
|
% FAILURE_TYPE = 'Error'
|
|
% THROWS = 'throws '
|
|
% else:
|
|
% FAILURE_TYPE = 'Never'
|
|
% THROWS = ''
|
|
% end
|
|
|
|
#if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY && !SWIFT_CONCURRENCY_EMBEDDED
|
|
@available(SwiftStdlib 5.9, *)
|
|
extension Task where Failure == ${FAILURE_TYPE} {
|
|
|
|
@_spi(MainActorUtilities)
|
|
@MainActor
|
|
@available(SwiftStdlib 5.9, *)
|
|
@discardableResult
|
|
@available(*, deprecated, renamed: "immediate")
|
|
public static func startOnMainActor(
|
|
priority: TaskPriority? = nil,
|
|
@_inheritActorContext @_implicitSelfCapture _ operation: __owned @Sendable @escaping @MainActor () async ${THROWS} -> Success
|
|
) -> Task<Success, ${FAILURE_TYPE}> {
|
|
let flags = taskCreateFlags(
|
|
priority: priority,
|
|
isChildTask: false,
|
|
copyTaskLocals: true,
|
|
inheritContext: true,
|
|
enqueueJob: false,
|
|
addPendingGroupTaskUnconditionally: false,
|
|
isDiscardingTask: false,
|
|
isSynchronousStart: false
|
|
)
|
|
|
|
let (task, _) = Builtin.createAsyncTask(flags, operation)
|
|
_startTaskOnMainActor(task)
|
|
|
|
return Task<Success, ${FAILURE_TYPE}>(task)
|
|
}
|
|
}
|
|
#endif
|
|
% end
|
|
|
|
// Internal Runtime Functions --------------------------------------------------
|
|
|
|
@_silgen_name("swift_task_startOnMainActor")
|
|
internal func _startTaskOnMainActor(_ task: Builtin.NativeObject)
|
|
|
|
@available(SwiftStdlib 6.2, *)
|
|
@_silgen_name("swift_task_immediate")
|
|
@usableFromInline
|
|
internal func _startTaskImmediately(_ task: Builtin.NativeObject, targetExecutor: Builtin.Executor?)
|