mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Since the "operation" inherits the context of the actor it should also be possible to reference its members without explicit use of `self.`.
284 lines
9.4 KiB
Swift
284 lines
9.4 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.immediate ---------------------------------------------------------
|
|
|
|
% METHOD_VARIANTS = [
|
|
% 'THROWING',
|
|
% 'NON_THROWING',
|
|
% ]
|
|
% OPERATION_PARAM = '@_inheritActorContext @_implicitSelfCapture _ operation: __owned @Sendable @escaping () async throws -> Success'
|
|
% for METHOD_VARIANT in METHOD_VARIANTS:
|
|
|
|
% IS_THROWING = METHOD_VARIANT == 'THROWING'
|
|
% if IS_THROWING:
|
|
% FAILURE_TYPE = 'Error'
|
|
% else:
|
|
% FAILURE_TYPE = 'Never'
|
|
% end
|
|
|
|
@available(SwiftStdlib 6.2, *)
|
|
extension Task where Failure == ${FAILURE_TYPE} {
|
|
|
|
@available(SwiftStdlib 6.2, *)
|
|
@available(*, deprecated, renamed: "immediate")
|
|
@discardableResult
|
|
public static func startSynchronously(
|
|
name: String? = nil,
|
|
priority: TaskPriority? = nil,
|
|
@_implicitSelfCapture @_inheritActorContext(always) _ operation: sending @isolated(any) @escaping () async throws -> Success
|
|
) -> Task<Success, ${FAILURE_TYPE}> {
|
|
immediate(name: name, priority: priority, operation: operation)
|
|
}
|
|
|
|
/// Create and immediately start running a new task 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 ``Task/init``.
|
|
///
|
|
/// - 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).
|
|
/// - 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 immediate(
|
|
name: String? = nil,
|
|
priority: TaskPriority? = nil,
|
|
% # NOTE: This closure cannot be 'sending' because we'll trigger ' pattern that the region based isolation checker does not understand how to check'
|
|
% # In this case: `func syncOnMyGlobalActor() { Task.immediate { @MyGlobalActor in } }`
|
|
@_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 not target executor, we can run synchronously
|
|
}
|
|
|
|
let flags = taskCreateFlags(
|
|
priority: priority,
|
|
isChildTask: false,
|
|
copyTaskLocals: true,
|
|
inheritContext: true,
|
|
enqueueJob: !canRunSynchronously,
|
|
addPendingGroupTaskUnconditionally: false,
|
|
isDiscardingTask: false,
|
|
isSynchronousStart: true
|
|
)
|
|
|
|
var task: Builtin.NativeObject?
|
|
#if $BuiltinCreateAsyncTaskName
|
|
if let name {
|
|
task =
|
|
unsafe name.utf8CString.withUnsafeBufferPointer { nameBytes in
|
|
Builtin.createTask(
|
|
flags: flags,
|
|
initialSerialExecutor: builtinSerialExecutor,
|
|
taskName: nameBytes.baseAddress!._rawValue,
|
|
operation: operation).0
|
|
}
|
|
}
|
|
#endif
|
|
if task == nil {
|
|
// either no task name was set, or names are unsupported
|
|
task = Builtin.createTask(
|
|
flags: flags,
|
|
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:
|
|
@available(SwiftStdlib 6.2, *)
|
|
extension ${GROUP_TYPE} {
|
|
|
|
/// Create and immediately start running a new child task 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``.
|
|
@available(SwiftStdlib 6.2, *)
|
|
@_alwaysEmitIntoClient
|
|
public func ${METHOD_NAME}( // in ${GROUP_TYPE}
|
|
name: String? = nil,
|
|
priority: TaskPriority? = nil,
|
|
@_inheritActorContext @_implicitSelfCapture operation: sending @isolated(any) @escaping () async ${THROWS}-> ${RESULT_TYPE}
|
|
) {
|
|
let flags = taskCreateFlags(
|
|
priority: priority,
|
|
isChildTask: true,
|
|
copyTaskLocals: false,
|
|
inheritContext: false,
|
|
enqueueJob: false, // don't enqueue, we'll run it manually
|
|
addPendingGroupTaskUnconditionally: true,
|
|
isDiscardingTask: ${'true' if 'Discarding' in GROUP_TYPE else 'false'},
|
|
isSynchronousStart: true
|
|
)
|
|
|
|
// Create the asynchronous task.
|
|
let builtinSerialExecutor =
|
|
unsafe Builtin.extractFunctionIsolation(operation)?.unownedExecutor.executor
|
|
|
|
// Create the task in this group.
|
|
let (task, _) = Builtin.createTask(
|
|
flags: flags,
|
|
initialSerialExecutor: builtinSerialExecutor,
|
|
taskGroup: self._group,
|
|
operation: operation
|
|
)
|
|
_startTaskImmediately(task, targetExecutor: builtinSerialExecutor)
|
|
}
|
|
}
|
|
% end # METHOD_NAMES
|
|
%end # GROUP_TYPES
|
|
|
|
// ==== Legacy SPI -------------------------------------------------------------
|
|
|
|
% METHOD_VARIANTS = [
|
|
% 'THROWING',
|
|
% 'NON_THROWING',
|
|
% ]
|
|
% OPERATION_PARAM = '@_inheritActorContext @_implicitSelfCapture _ operation: __owned @Sendable @escaping @MainActor () async throws -> Success'
|
|
% for METHOD_VARIANT in METHOD_VARIANTS:
|
|
|
|
% IS_THROWING = METHOD_VARIANT == 'THROWING'
|
|
% if IS_THROWING:
|
|
% FAILURE_TYPE = 'Error'
|
|
% else:
|
|
% FAILURE_TYPE = 'Never'
|
|
% OPERATION_PARAM = OPERATION_PARAM.replace('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
|
|
public static func startOnMainActor(
|
|
priority: TaskPriority? = nil,
|
|
${OPERATION_PARAM}
|
|
) -> 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?)
|