mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
1078 lines
35 KiB
Swift
1078 lines
35 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 SwiftConcurrencyInternalShims
|
|
|
|
// ==== Task -------------------------------------------------------------------
|
|
|
|
/// A unit of asynchronous work.
|
|
///
|
|
/// When you create an instance of `Task`,
|
|
/// you provide a closure that contains the work for that task to perform.
|
|
/// Tasks can start running immediately after creation;
|
|
/// you don't explicitly start or schedule them.
|
|
/// After creating a task, you use the instance to interact with it ---
|
|
/// for example, to wait for it to complete or to cancel it.
|
|
/// It's not a programming error to discard a reference to a task
|
|
/// without waiting for that task to finish or canceling it.
|
|
/// A task runs regardless of whether you keep a reference to it.
|
|
/// However, if you discard the reference to a task,
|
|
/// you give up the ability
|
|
/// to wait for that task's result or cancel the task.
|
|
///
|
|
/// To support operations on the current task,
|
|
/// which can be either a detached task or child task,
|
|
/// `Task` also exposes class methods like `yield()`.
|
|
/// Because these methods are asynchronous,
|
|
/// they're always invoked as part of an existing task.
|
|
///
|
|
/// Only code that's running as part of the task can interact with that task.
|
|
/// To interact with the current task,
|
|
/// you call one of the static methods on `Task`.
|
|
///
|
|
/// A task's execution can be seen as a series of periods where the task ran.
|
|
/// Each such period ends at a suspension point or the
|
|
/// completion of the task.
|
|
/// These periods of execution are represented by instances of `PartialAsyncTask`.
|
|
/// Unless you're implementing a custom executor,
|
|
/// you don't directly interact with partial tasks.
|
|
///
|
|
/// For information about the language-level concurrency model that `Task` 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/
|
|
///
|
|
/// Task Cancellation
|
|
/// =================
|
|
///
|
|
/// Tasks include a shared mechanism for indicating cancellation,
|
|
/// but not a shared implementation for how to handle cancellation.
|
|
/// Depending on the work you're doing in the task,
|
|
/// the correct way to stop that work varies.
|
|
/// Likewise,
|
|
/// it's the responsibility of the code running as part of the task
|
|
/// to check for cancellation whenever stopping is appropriate.
|
|
/// In a long-task that includes multiple pieces,
|
|
/// you might need to check for cancellation at several points,
|
|
/// and handle cancellation differently at each point.
|
|
/// If you only need to throw an error to stop the work,
|
|
/// call the `Task.checkCancellation()` function to check for cancellation.
|
|
/// Other responses to cancellation include
|
|
/// returning the work completed so far, returning an empty result, or returning `nil`.
|
|
///
|
|
/// Cancellation is a purely Boolean state;
|
|
/// there's no way to include additional information
|
|
/// like the reason for cancellation.
|
|
/// This reflects the fact that a task can be canceled for many reasons,
|
|
/// and additional reasons can accrue during the cancellation process.
|
|
///
|
|
/// ### Task closure lifetime
|
|
/// Tasks are initialized by passing a closure containing the code that will be executed by a given task.
|
|
///
|
|
/// After this code has run to completion, the task has completed, resulting in either
|
|
/// a failure or result value, this closure is eagerly released.
|
|
///
|
|
/// Retaining a task object doesn't indefinitely retain the closure,
|
|
/// because any references that a task holds are released
|
|
/// after the task completes.
|
|
/// Consequently, tasks rarely need to capture weak references to values.
|
|
///
|
|
/// For example, in the following snippet of code it is not necessary to capture the actor as `weak`,
|
|
/// because as the task completes it'll let go of the actor reference, breaking the
|
|
/// reference cycle between the Task and the actor holding it.
|
|
///
|
|
/// ```
|
|
/// struct Work: Sendable {}
|
|
///
|
|
/// actor Worker {
|
|
/// var work: Task<Void, Never>?
|
|
/// var result: Work?
|
|
///
|
|
/// deinit {
|
|
/// // even though the task is still retained,
|
|
/// // once it completes it no longer causes a reference cycle with the actor
|
|
///
|
|
/// print("deinit actor")
|
|
/// }
|
|
///
|
|
/// func start() {
|
|
/// work = Task {
|
|
/// print("start task work")
|
|
/// try? await Task.sleep(for: .seconds(3))
|
|
/// self.result = Work() // we captured self
|
|
/// print("completed task work")
|
|
/// // but as the task completes, this reference is released
|
|
/// }
|
|
/// // we keep a strong reference to the task
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// And using it like this:
|
|
///
|
|
/// ```
|
|
/// await Worker().start()
|
|
/// ```
|
|
///
|
|
/// Note that the actor is only retained by the start() method's use of `self`,
|
|
/// and that the start method immediately returns, without waiting for the
|
|
/// unstructured `Task` to finish. Once the task is completed and its closure is
|
|
/// destroyed, the strong reference to the actor is also released allowing the
|
|
/// actor to deinitialize as expected.
|
|
///
|
|
/// Therefore, the above call will consistently result in the following output:
|
|
///
|
|
/// ```other
|
|
/// start task work
|
|
/// completed task work
|
|
/// deinit actor
|
|
/// ```
|
|
@available(SwiftStdlib 5.1, *)
|
|
@frozen
|
|
public struct Task<Success: Sendable, Failure: Error>: Sendable {
|
|
@usableFromInline
|
|
internal let _task: Builtin.NativeObject
|
|
|
|
@_alwaysEmitIntoClient
|
|
internal init(_ task: Builtin.NativeObject) {
|
|
self._task = task
|
|
}
|
|
}
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
extension Task {
|
|
/// The result from a throwing task, after it completes.
|
|
///
|
|
/// If the task hasn't completed,
|
|
/// accessing this property waits for it to complete
|
|
/// and its priority increases to that of the current task.
|
|
/// Note that this might not be as effective as
|
|
/// creating the task with the correct priority,
|
|
/// depending on the executor's scheduling details.
|
|
///
|
|
/// If the task throws an error, this property propagates that error.
|
|
/// Tasks that respond to cancellation by throwing `CancellationError`
|
|
/// have that error propagated here upon cancellation.
|
|
///
|
|
/// - Returns: The task's result.
|
|
public var 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,
|
|
/// accessing this property waits for it to complete
|
|
/// and its priority increases to that of the current task.
|
|
/// Note that this might not be as effective as
|
|
/// creating the task with the correct priority,
|
|
/// depending on the executor's scheduling details.
|
|
///
|
|
/// - Returns: If the task succeeded,
|
|
/// `.success` with the task's result as the associated value;
|
|
/// otherwise, `.failure` with the error as the associated value.
|
|
public var result: Result<Success, Failure> {
|
|
get async {
|
|
do {
|
|
return .success(try await value)
|
|
} catch {
|
|
return .failure(error as! Failure) // as!-safe, guaranteed to be Failure
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Cancels this task.
|
|
///
|
|
/// Cancelling a task has three primary effects:
|
|
///
|
|
/// - It flags the task as canceled.
|
|
/// - It causes any active cancellation handlers on the task to run, once.
|
|
/// - It cancels any child tasks and task groups of the task, including
|
|
/// those created in the future. If those tasks have cancellation handlers,
|
|
/// they also are triggered.
|
|
///
|
|
/// Task cancellation is cooperative and idempotent.
|
|
///
|
|
/// Cancelling a task does not automatically cause arbitrary functions on the task
|
|
/// to stop running or throw errors. A function _may_ choose to react
|
|
/// to cancellation by ending its work early, and it is conventional to
|
|
/// signal that to callers by throwing `CancellationError`. However,
|
|
/// a function that doesn't specifically check for cancellation will
|
|
/// run to completion normally, even if the task it is running on is
|
|
/// canceled. However, that function might still end early if it calls
|
|
/// other code that handles cancellation by throwing and that function doesn't
|
|
/// handle the error.
|
|
///
|
|
/// It's safe to cancel a task from any task or thread. It's safe for
|
|
/// multiple tasks or threads to cancel the same task at the same
|
|
/// time. Cancelling a task that has already been canceled has no
|
|
/// additional effect.
|
|
///
|
|
/// `cancel` may need to acquire locks and synchronously run
|
|
/// arbitrary cancellation-handler code associated with the
|
|
/// canceled task. To reduce the risk of deadlock, it is
|
|
/// recommended that callers release any locks they might be
|
|
/// holding before they call cancel.
|
|
///
|
|
/// If the task has already run past the last point where it could have
|
|
/// performed a cancellation check, cancelling it may have no observable effects.
|
|
///
|
|
/// - SeeAlso: `Task.checkCancellation()`
|
|
/// - SeeAlso: `withTaskCancellationHandler(operation:onCancel:isolation:)`
|
|
public func cancel() {
|
|
Builtin.cancelAsyncTask(_task)
|
|
}
|
|
}
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
extension Task where Failure == Never {
|
|
/// The result from a nonthrowing task, after it completes.
|
|
///
|
|
/// If the task hasn't completed yet,
|
|
/// accessing this property waits for it to complete
|
|
/// and its priority increases to that of the current task.
|
|
/// Note that this might not be as effective as
|
|
/// creating the task with the correct priority,
|
|
/// depending on the executor's scheduling details.
|
|
///
|
|
/// Tasks that never throw an error can still check for cancellation,
|
|
/// but they need to use an approach like returning `nil`
|
|
/// instead of throwing an error.
|
|
public var value: Success {
|
|
get async {
|
|
return await _taskFutureGet(_task)
|
|
}
|
|
}
|
|
}
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
extension Task: Hashable {
|
|
public func hash(into hasher: inout Hasher) {
|
|
UnsafeRawPointer(Builtin.bridgeToRawPointer(_task)).hash(into: &hasher)
|
|
}
|
|
}
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
extension Task: Equatable {
|
|
public static func ==(lhs: Self, rhs: Self) -> Bool {
|
|
unsafe UnsafeRawPointer(Builtin.bridgeToRawPointer(lhs._task)) ==
|
|
UnsafeRawPointer(Builtin.bridgeToRawPointer(rhs._task))
|
|
}
|
|
}
|
|
|
|
// ==== Task Priority ----------------------------------------------------------
|
|
|
|
/// The priority of a task.
|
|
///
|
|
/// The executor determines how priority information affects the way tasks are scheduled.
|
|
/// The behavior varies depending on the executor currently being used.
|
|
/// Typically, executors attempt to run tasks with a higher priority
|
|
/// before tasks with a lower priority.
|
|
/// However, the semantics of how priority is treated are left up to each
|
|
/// platform and `Executor` implementation.
|
|
///
|
|
/// Child tasks automatically inherit their parent task's priority.
|
|
/// Detached tasks created by `detach(priority:operation:)` don't inherit task priority
|
|
/// because they aren't attached to the current task.
|
|
///
|
|
/// In some situations the priority of a task is elevated ---
|
|
/// that is, the task is treated as it if had a higher priority,
|
|
/// without actually changing the priority of the task:
|
|
///
|
|
/// - If a task runs on behalf of an actor,
|
|
/// and a new higher-priority task is enqueued to the actor,
|
|
/// then the actor's current task is temporarily elevated
|
|
/// to the priority of the enqueued task.
|
|
/// This priority elevation allows the new task
|
|
/// to be processed at the priority it was enqueued with.
|
|
/// - If a higher-priority task calls the `get()` method,
|
|
/// then the priority of this task increases until the task completes.
|
|
///
|
|
/// In both cases, priority elevation helps you prevent a low-priority task
|
|
/// from blocking the execution of a high priority task,
|
|
/// which is also known as *priority inversion*.
|
|
@available(SwiftStdlib 5.1, *)
|
|
public struct TaskPriority: RawRepresentable, Sendable {
|
|
public typealias RawValue = UInt8
|
|
public var rawValue: UInt8
|
|
|
|
public init(rawValue: UInt8) {
|
|
self.rawValue = rawValue
|
|
}
|
|
|
|
public static let high: TaskPriority = .init(rawValue: 0x19)
|
|
|
|
@_alwaysEmitIntoClient
|
|
public static var medium: TaskPriority {
|
|
.init(rawValue: 0x15)
|
|
}
|
|
|
|
public static let low: TaskPriority = .init(rawValue: 0x11)
|
|
|
|
public static let userInitiated: TaskPriority = high
|
|
public static let utility: TaskPriority = low
|
|
public static let background: TaskPriority = .init(rawValue: 0x09)
|
|
|
|
@available(*, deprecated, renamed: "medium")
|
|
public static let `default`: TaskPriority = .init(rawValue: 0x15)
|
|
}
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
extension TaskPriority: Equatable {
|
|
public static func == (lhs: TaskPriority, rhs: TaskPriority) -> Bool {
|
|
lhs.rawValue == rhs.rawValue
|
|
}
|
|
|
|
public static func != (lhs: TaskPriority, rhs: TaskPriority) -> Bool {
|
|
lhs.rawValue != rhs.rawValue
|
|
}
|
|
}
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
extension TaskPriority: Comparable {
|
|
public static func < (lhs: TaskPriority, rhs: TaskPriority) -> Bool {
|
|
lhs.rawValue < rhs.rawValue
|
|
}
|
|
|
|
public static func <= (lhs: TaskPriority, rhs: TaskPriority) -> Bool {
|
|
lhs.rawValue <= rhs.rawValue
|
|
}
|
|
|
|
public static func > (lhs: TaskPriority, rhs: TaskPriority) -> Bool {
|
|
lhs.rawValue > rhs.rawValue
|
|
}
|
|
|
|
public static func >= (lhs: TaskPriority, rhs: TaskPriority) -> Bool {
|
|
lhs.rawValue >= rhs.rawValue
|
|
}
|
|
}
|
|
|
|
|
|
@available(SwiftStdlib 5.9, *)
|
|
@_unavailableInEmbedded
|
|
extension TaskPriority: CustomStringConvertible {
|
|
@available(SwiftStdlib 5.9, *)
|
|
public var description: String {
|
|
switch self.rawValue {
|
|
case Self.low.rawValue:
|
|
return "\(Self.self).low"
|
|
case Self.medium.rawValue:
|
|
return "\(Self.self).medium"
|
|
case Self.high.rawValue:
|
|
return "\(Self.self).high"
|
|
case Self.background.rawValue:
|
|
return "\(Self.self).background"
|
|
default:
|
|
return "\(Self.self)(rawValue: \(self.rawValue))"
|
|
}
|
|
}
|
|
}
|
|
|
|
#if !SWIFT_CONCURRENCY_EMBEDDED
|
|
@available(SwiftStdlib 5.1, *)
|
|
extension TaskPriority: Codable { }
|
|
#endif
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
extension Task where Success == Never, Failure == Never {
|
|
|
|
/// The current task's priority.
|
|
///
|
|
/// If you access this property outside of any task,
|
|
/// this queries the system to determine the
|
|
/// priority at which the current function is running.
|
|
/// If the system can't provide a priority,
|
|
/// this property's value is `Priority.default`.
|
|
public static var currentPriority: TaskPriority {
|
|
unsafe withUnsafeCurrentTask { unsafeTask in
|
|
// If we are running on behalf of a task, use that task's priority.
|
|
if let unsafeTask = unsafe unsafeTask {
|
|
return unsafe unsafeTask.priority
|
|
}
|
|
|
|
// Otherwise, query the system.
|
|
return TaskPriority(rawValue: UInt8(_getCurrentThreadPriority()))
|
|
}
|
|
}
|
|
|
|
/// The current task's base priority.
|
|
///
|
|
/// If you access this property outside of any task, this returns nil
|
|
@available(SwiftStdlib 5.7, *)
|
|
public static var basePriority: TaskPriority? {
|
|
unsafe withUnsafeCurrentTask { task in
|
|
// If we are running on behalf of a task, use that task's priority.
|
|
if let unsafeTask = unsafe task {
|
|
return unsafe TaskPriority(rawValue: _taskBasePriority(unsafeTask._task))
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
extension TaskPriority {
|
|
/// Downgrade user-interactive to user-initiated.
|
|
var _downgradeUserInteractive: TaskPriority {
|
|
return self
|
|
}
|
|
}
|
|
|
|
// ==== Job Flags --------------------------------------------------------------
|
|
|
|
/// Flags for schedulable jobs.
|
|
///
|
|
/// This is a port of the C++ FlagSet.
|
|
@available(SwiftStdlib 5.1, *)
|
|
struct JobFlags {
|
|
/// Kinds of schedulable jobs.
|
|
enum Kind: Int32 {
|
|
case task = 0
|
|
}
|
|
|
|
/// The actual bit representation of these flags.
|
|
var bits: Int32 = 0
|
|
|
|
/// The kind of job described by these flags.
|
|
var kind: Kind {
|
|
get {
|
|
Kind(rawValue: bits & 0xFF)!
|
|
}
|
|
|
|
set {
|
|
bits = (bits & ~0xFF) | newValue.rawValue
|
|
}
|
|
}
|
|
|
|
/// Whether this is an asynchronous task.
|
|
var isAsyncTask: Bool { kind == .task }
|
|
|
|
/// The priority given to the job.
|
|
var priority: TaskPriority? {
|
|
get {
|
|
let value = (Int(bits) & 0xFF00) >> 8
|
|
|
|
if value == 0 {
|
|
return nil
|
|
}
|
|
|
|
return TaskPriority(rawValue: UInt8(value))
|
|
}
|
|
|
|
set {
|
|
bits = (bits & ~0xFF00) | Int32((Int(newValue?.rawValue ?? 0) << 8))
|
|
}
|
|
}
|
|
|
|
/// Whether this is a child task.
|
|
var isChildTask: Bool {
|
|
get {
|
|
(bits & (1 << 24)) != 0
|
|
}
|
|
|
|
set {
|
|
if newValue {
|
|
bits = bits | 1 << 24
|
|
} else {
|
|
bits = (bits & ~(1 << 24))
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Whether this is a future.
|
|
var isFuture: Bool {
|
|
get {
|
|
(bits & (1 << 25)) != 0
|
|
}
|
|
|
|
set {
|
|
if newValue {
|
|
bits = bits | 1 << 25
|
|
} else {
|
|
bits = (bits & ~(1 << 25))
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Whether this is a group child.
|
|
var isGroupChildTask: Bool {
|
|
get {
|
|
(bits & (1 << 26)) != 0
|
|
}
|
|
|
|
set {
|
|
if newValue {
|
|
bits = bits | 1 << 26
|
|
} else {
|
|
bits = (bits & ~(1 << 26))
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Whether this is a task created by the 'async' operation, which
|
|
/// conceptually continues the work of the synchronous code that invokes
|
|
/// it.
|
|
var isContinuingAsyncTask: Bool {
|
|
get {
|
|
(bits & (1 << 27)) != 0
|
|
}
|
|
|
|
set {
|
|
if newValue {
|
|
bits = bits | 1 << 27
|
|
} else {
|
|
bits = (bits & ~(1 << 27))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ==== Task Creation Flags --------------------------------------------------
|
|
|
|
/// Form task creation flags for use with the createAsyncTask builtins.
|
|
@available(SwiftStdlib 5.1, *)
|
|
@_alwaysEmitIntoClient
|
|
func taskCreateFlags(
|
|
priority: TaskPriority?, isChildTask: Bool, copyTaskLocals: Bool,
|
|
inheritContext: Bool, enqueueJob: Bool,
|
|
addPendingGroupTaskUnconditionally: Bool,
|
|
isDiscardingTask: Bool,
|
|
isSynchronousStart: Bool
|
|
) -> Int {
|
|
var bits = 0
|
|
bits |= (bits & ~0xFF) | Int(priority?.rawValue ?? 0)
|
|
if isChildTask {
|
|
bits |= 1 << 8
|
|
}
|
|
if copyTaskLocals {
|
|
bits |= 1 << 10
|
|
}
|
|
if inheritContext {
|
|
bits |= 1 << 11
|
|
}
|
|
if enqueueJob {
|
|
bits |= 1 << 12
|
|
}
|
|
if addPendingGroupTaskUnconditionally {
|
|
bits |= 1 << 13
|
|
}
|
|
if isDiscardingTask {
|
|
bits |= 1 << 14
|
|
}
|
|
// 15 is used by 'IsTaskFunctionConsumed'
|
|
if isSynchronousStart {
|
|
bits |= 1 << 16
|
|
}
|
|
return bits
|
|
}
|
|
|
|
// ==== Task Name --------------------------------------------------------------
|
|
|
|
@available(SwiftStdlib 6.2, *)
|
|
extension Task where Success == Never, Failure == Never {
|
|
|
|
/// Returns the human-readable name of the current task,
|
|
/// if it was set during the tasks' creation.
|
|
///
|
|
/// Tasks can be named during their creation, which can be helpful to identify
|
|
/// unique tasks which may be created at same source locations, for example:
|
|
///
|
|
/// func process(items: [Int]) async {
|
|
/// await withTaskGroup { group in
|
|
/// for item in items {
|
|
/// group.addTask(name: "process-\(item)") {
|
|
/// await process(item)
|
|
/// }
|
|
/// }
|
|
/// }
|
|
/// }
|
|
///
|
|
/// ### Task name availability
|
|
/// The task name is only available when running with a recent runtime (Swift 6.2+).
|
|
///
|
|
/// ``Task`` initializers which may accept a task name are more available than this property,
|
|
/// for convenience purposes, in order to not have to set task names conditionally
|
|
/// however their effect is runtime dependent, and is reflected in the availability of this property.
|
|
@available(SwiftStdlib 6.2, *)
|
|
public static var name: String? {
|
|
return _getCurrentTaskNameString()
|
|
}
|
|
}
|
|
|
|
// ==== Voluntary Suspension -----------------------------------------------------
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
extension Task where Success == Never, Failure == Never {
|
|
|
|
/// Suspends the current task and allows other tasks to execute.
|
|
///
|
|
/// A task can voluntarily suspend itself
|
|
/// in the middle of a long-running operation
|
|
/// that doesn't contain any suspension points,
|
|
/// to let other tasks run for a while
|
|
/// before execution returns to this task.
|
|
///
|
|
/// If this task is the highest-priority task in the system,
|
|
/// the executor immediately resumes execution of the same task.
|
|
/// As such,
|
|
/// this method isn't necessarily a way to avoid resource starvation.
|
|
public static func yield() async {
|
|
return await Builtin.withUnsafeContinuation { (continuation: Builtin.RawUnsafeContinuation) -> Void in
|
|
let job = _taskCreateNullaryContinuationJob(
|
|
priority: Int(Task.currentPriority.rawValue),
|
|
continuation: continuation)
|
|
|
|
#if !$Embedded && !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
|
|
if #available(SwiftStdlibCurrentOS 6.2, *) {
|
|
let executor = Task.currentExecutor
|
|
|
|
executor.enqueue(ExecutorJob(context: job))
|
|
} else {
|
|
fatalError("we shouldn't get here; if we have, availability is broken")
|
|
}
|
|
#else
|
|
_enqueueJobGlobal(job)
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
// ==== UnsafeCurrentTask ------------------------------------------------------
|
|
|
|
/// Calls a closure with an unsafe reference to the current task.
|
|
///
|
|
/// If you call this function from the body of an asynchronous function,
|
|
/// the unsafe task handle passed to the closure is always non-`nil`
|
|
/// because an asynchronous function always runs in the context of a task.
|
|
/// However, if you call this function from the body of a synchronous function,
|
|
/// and that function isn't executing in the context of any task,
|
|
/// the unsafe task handle is `nil`.
|
|
///
|
|
/// Don't store an unsafe task reference
|
|
/// for use outside this method's closure.
|
|
/// Storing an unsafe reference doesn't affect the task's actual life cycle,
|
|
/// and the behavior of accessing an unsafe task reference
|
|
/// outside of the `withUnsafeCurrentTask(body:)` method's closure isn't defined.
|
|
/// There's no safe way to retrieve a reference to the current task
|
|
/// and save it for long-term use.
|
|
/// To query the current task without saving a reference to it,
|
|
/// use properties like `currentPriority`.
|
|
/// If you need to store a reference to a task,
|
|
/// create an unstructured task using `Task.detached(priority:operation:)` instead.
|
|
///
|
|
/// - Parameters:
|
|
/// - body: A closure that takes an `UnsafeCurrentTask` parameter.
|
|
/// If `body` has a return value,
|
|
/// that value is also used as the return value
|
|
/// for the `withUnsafeCurrentTask(body:)` function.
|
|
///
|
|
/// - Returns: The return value, if any, of the `body` closure.
|
|
@available(SwiftStdlib 5.1, *)
|
|
public func withUnsafeCurrentTask<T>(body: (UnsafeCurrentTask?) throws -> T) rethrows -> T {
|
|
guard let _task = _getCurrentAsyncTask() else {
|
|
return try body(nil)
|
|
}
|
|
|
|
// FIXME: This retain seems pretty wrong, however if we don't we WILL crash
|
|
// with "destroying a task that never completed" in the task's destroy.
|
|
// How do we solve this properly?
|
|
Builtin.retain(_task)
|
|
|
|
return try unsafe body(UnsafeCurrentTask(_task))
|
|
}
|
|
|
|
@available(SwiftStdlib 6.0, *)
|
|
public func withUnsafeCurrentTask<T>(body: (UnsafeCurrentTask?) async throws -> T) async rethrows -> T {
|
|
guard let _task = _getCurrentAsyncTask() else {
|
|
return try await body(nil)
|
|
}
|
|
|
|
// FIXME: This retain seems pretty wrong, however if we don't we WILL crash
|
|
// with "destroying a task that never completed" in the task's destroy.
|
|
// How do we solve this properly?
|
|
Builtin.retain(_task)
|
|
|
|
return try unsafe await body(UnsafeCurrentTask(_task))
|
|
}
|
|
|
|
/// An unsafe reference to the current task.
|
|
///
|
|
/// To get an instance of `UnsafeCurrentTask` for the current task,
|
|
/// call the `withUnsafeCurrentTask(body:)` method.
|
|
/// Don't store an unsafe task reference
|
|
/// for use outside that method's closure.
|
|
/// Storing an unsafe reference doesn't affect the task's actual life cycle,
|
|
/// and the behavior of accessing an unsafe task reference
|
|
/// outside of the `withUnsafeCurrentTask(body:)` method's closure isn't defined.
|
|
///
|
|
/// Only APIs on `UnsafeCurrentTask` that are also part of `Task`
|
|
/// are safe to invoke from a task other than
|
|
/// the task that this `UnsafeCurrentTask` instance refers to.
|
|
/// Calling other APIs from another task is undefined behavior,
|
|
/// breaks invariants in other parts of the program running on this task,
|
|
/// and may lead to crashes or data loss.
|
|
///
|
|
/// For information about the language-level concurrency model that `UnsafeCurrentTask` 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/
|
|
@available(SwiftStdlib 5.1, *)
|
|
@unsafe
|
|
public struct UnsafeCurrentTask {
|
|
internal let _task: Builtin.NativeObject
|
|
|
|
// May only be created by the standard library.
|
|
internal init(_ task: Builtin.NativeObject) {
|
|
unsafe self._task = task
|
|
}
|
|
|
|
/// A Boolean value that indicates whether the current task was canceled.
|
|
///
|
|
/// After the value of this property becomes `true`, it remains `true` indefinitely.
|
|
/// There is no way to uncancel a task.
|
|
///
|
|
/// - SeeAlso: `checkCancellation()`
|
|
public var isCancelled: Bool {
|
|
unsafe _taskIsCancelled(_task)
|
|
}
|
|
|
|
/// The current task's priority.
|
|
///
|
|
/// - SeeAlso: `TaskPriority`
|
|
/// - SeeAlso: `Task.currentPriority`
|
|
public var priority: TaskPriority {
|
|
unsafe TaskPriority(rawValue: _taskCurrentPriority(_task))
|
|
}
|
|
|
|
/// The current task's base priority.
|
|
///
|
|
/// - SeeAlso: `TaskPriority`
|
|
/// - SeeAlso: `Task.basePriority`
|
|
@available(SwiftStdlib 5.9, *)
|
|
public var basePriority: TaskPriority {
|
|
unsafe TaskPriority(rawValue: _taskBasePriority(_task))
|
|
}
|
|
|
|
/// Cancel the current task.
|
|
public func cancel() {
|
|
unsafe _taskCancel(_task)
|
|
}
|
|
}
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
@available(*, unavailable)
|
|
extension UnsafeCurrentTask: Sendable { }
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
extension UnsafeCurrentTask: @unsafe Hashable {
|
|
public func hash(into hasher: inout Hasher) {
|
|
unsafe UnsafeRawPointer(Builtin.bridgeToRawPointer(_task)).hash(into: &hasher)
|
|
}
|
|
}
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
extension UnsafeCurrentTask: Equatable {
|
|
public static func ==(lhs: Self, rhs: Self) -> Bool {
|
|
unsafe UnsafeRawPointer(Builtin.bridgeToRawPointer(lhs._task)) ==
|
|
UnsafeRawPointer(Builtin.bridgeToRawPointer(rhs._task))
|
|
}
|
|
}
|
|
|
|
// ==== Internal ---------------------------------------------------------------
|
|
@available(SwiftStdlib 5.1, *)
|
|
@_silgen_name("swift_task_getCurrent")
|
|
public func _getCurrentAsyncTask() -> Builtin.NativeObject?
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
@_silgen_name("swift_task_getJobFlags")
|
|
func getJobFlags(_ task: Builtin.NativeObject) -> JobFlags
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
@_silgen_name("swift_task_enqueueGlobal")
|
|
@usableFromInline
|
|
func _enqueueJobGlobal(_ task: Builtin.Job)
|
|
|
|
@available(SwiftStdlib 5.9, *)
|
|
func _enqueueJobGlobal(_ task: UnownedJob) {
|
|
_enqueueJobGlobal(task._context)
|
|
}
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
@_silgen_name("swift_task_enqueueGlobalWithDelay")
|
|
@usableFromInline
|
|
func _enqueueJobGlobalWithDelay(_ delay: UInt64, _ task: Builtin.Job)
|
|
|
|
@available(SwiftStdlib 5.9, *)
|
|
func _enqueueJobGlobalWithDelay(_ delay: UInt64, _ task: UnownedJob) {
|
|
return _enqueueJobGlobalWithDelay(delay, task._context)
|
|
}
|
|
|
|
@available(SwiftStdlibCurrentOS 5.7, *)
|
|
@_silgen_name("swift_task_enqueueGlobalWithDeadline")
|
|
@usableFromInline
|
|
func _enqueueJobGlobalWithDeadline(_ seconds: Int64, _ nanoseconds: Int64,
|
|
_ toleranceSec: Int64, _ toleranceNSec: Int64,
|
|
_ clock: Int32, _ task: UnownedJob)
|
|
|
|
@usableFromInline
|
|
@available(SwiftStdlib 6.2, *)
|
|
@_silgen_name("swift_task_addPriorityEscalationHandler")
|
|
func _taskAddPriorityEscalationHandler(
|
|
handler: (UInt8, UInt8) -> Void
|
|
) -> UnsafeRawPointer /*EscalationNotificationStatusRecord*/
|
|
|
|
@usableFromInline
|
|
@available(SwiftStdlib 6.2, *)
|
|
@_silgen_name("swift_task_removePriorityEscalationHandler")
|
|
func _taskRemovePriorityEscalationHandler(
|
|
record: UnsafeRawPointer /*EscalationNotificationStatusRecord*/
|
|
)
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
@usableFromInline
|
|
@_silgen_name("swift_task_asyncMainDrainQueue")
|
|
internal func _asyncMainDrainQueue() -> Never
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
@usableFromInline
|
|
@_silgen_name("swift_task_getMainExecutor")
|
|
internal func _getMainExecutor() -> Builtin.Executor
|
|
|
|
/// Get the default generic serial executor.
|
|
///
|
|
/// It is used by default used by tasks and default actors,
|
|
/// unless other executors are specified.
|
|
@available(SwiftStdlib 6.0, *)
|
|
@usableFromInline
|
|
internal func _getGenericSerialExecutor() -> Builtin.Executor {
|
|
// The `SerialExecutorRef` to "default generic executor" is guaranteed
|
|
// to be a zero value;
|
|
//
|
|
// As the runtime relies on this in multiple places,
|
|
// so instead of a runtime call to get this executor ref, we bitcast a "zero"
|
|
// of expected size to the builtin executor type.
|
|
unsafe unsafeBitCast((UInt(0), UInt(0)), to: Builtin.Executor.self)
|
|
}
|
|
|
|
#if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
|
|
@available(SwiftStdlib 5.1, *)
|
|
@available(*, unavailable, message: "Unavailable in task-to-thread concurrency model")
|
|
@usableFromInline
|
|
@preconcurrency
|
|
internal func _runAsyncMain(_ asyncFun: @Sendable @escaping () async throws -> ()) {
|
|
fatalError("Unavailable in task-to-thread concurrency model")
|
|
}
|
|
#elseif !SWIFT_CONCURRENCY_EMBEDDED
|
|
@available(SwiftStdlib 5.1, *)
|
|
@usableFromInline
|
|
@preconcurrency
|
|
internal func _runAsyncMain(_ asyncFun: @Sendable @escaping () async throws -> ()) {
|
|
let taskFlags = taskCreateFlags(
|
|
priority: nil, isChildTask: false, copyTaskLocals: false,
|
|
inheritContext: false, enqueueJob: false,
|
|
addPendingGroupTaskUnconditionally: false,
|
|
isDiscardingTask: false, isSynchronousStart: false)
|
|
|
|
let (theTask, _) = Builtin.createAsyncTask(taskFlags) {
|
|
do {
|
|
try await asyncFun()
|
|
exit(0)
|
|
} catch {
|
|
_errorInMain(error)
|
|
}
|
|
}
|
|
|
|
let job = Builtin.convertTaskToJob(theTask)
|
|
if #available(SwiftStdlibCurrentOS 6.2, *) {
|
|
MainActor.executor.enqueue(ExecutorJob(context: job))
|
|
} else {
|
|
fatalError("we shouldn't get here; if we have, availability is broken")
|
|
}
|
|
|
|
_asyncMainDrainQueue()
|
|
}
|
|
#endif
|
|
|
|
// FIXME: both of these ought to take their arguments _owned so that
|
|
// we can do a move out of the future in the common case where it's
|
|
// unreferenced
|
|
@available(SwiftStdlib 5.1, *)
|
|
@_silgen_name("swift_task_future_wait")
|
|
public func _taskFutureGet<T>(_ task: Builtin.NativeObject) async -> T
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
@_silgen_name("swift_task_future_wait_throwing")
|
|
public func _taskFutureGetThrowing<T>(_ task: Builtin.NativeObject) async throws -> T
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
@_silgen_name("swift_task_cancel")
|
|
public func _taskCancel(_ task: Builtin.NativeObject)
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
@_silgen_name("swift_task_isCancelled")
|
|
@usableFromInline
|
|
func _taskIsCancelled(_ task: Builtin.NativeObject) -> Bool
|
|
|
|
@_silgen_name("swift_task_currentPriority")
|
|
internal func _taskCurrentPriority(_ task: Builtin.NativeObject) -> UInt8
|
|
|
|
@available(SwiftStdlib 6.2, *)
|
|
@_silgen_name("swift_task_escalate")
|
|
@discardableResult
|
|
internal func _taskEscalate(_ task: Builtin.NativeObject, newPriority: UInt8) -> UInt8
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
@_silgen_name("swift_task_basePriority")
|
|
internal func _taskBasePriority(_ task: Builtin.NativeObject) -> UInt8
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
@_silgen_name("swift_task_createNullaryContinuationJob")
|
|
func _taskCreateNullaryContinuationJob(priority: Int, continuation: Builtin.RawUnsafeContinuation) -> Builtin.Job
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
@usableFromInline
|
|
@_silgen_name("swift_task_isCurrentExecutor")
|
|
func _taskIsCurrentExecutor(_ executor: Builtin.Executor) -> Bool
|
|
|
|
@available(SwiftStdlib 6.2, *)
|
|
@_silgen_name("swift_task_isCurrentExecutorWithFlags")
|
|
@usableFromInline
|
|
internal func _taskIsCurrentExecutor(
|
|
executor: Builtin.Executor, flags: UInt64) -> Bool
|
|
|
|
@available(SwiftStdlib 6.2, *)
|
|
extension GlobalActor {
|
|
@available(SwiftStdlib 6.2, *)
|
|
@_silgen_name("_swift_task_isCurrentGlobalActor")
|
|
internal static func _taskIsCurrentGlobalActor() -> Bool {
|
|
let executor = unsafe sharedUnownedExecutor
|
|
return unsafe _taskIsCurrentExecutor(executor: executor.executor, flags: 0)
|
|
}
|
|
}
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
@usableFromInline
|
|
@_silgen_name("swift_task_reportUnexpectedExecutor")
|
|
func _reportUnexpectedExecutor(_ _filenameStart: Builtin.RawPointer,
|
|
_ _filenameLength: Builtin.Word,
|
|
_ _filenameIsASCII: Builtin.Int1,
|
|
_ _line: Builtin.Word,
|
|
_ _executor: Builtin.Executor)
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
@_silgen_name("swift_task_getCurrentThreadPriority")
|
|
func _getCurrentThreadPriority() -> Int
|
|
|
|
@available(SwiftStdlib 6.2, *)
|
|
@_silgen_name("swift_task_getCurrentTaskName")
|
|
internal func _getCurrentTaskName() -> UnsafePointer<UInt8>?
|
|
|
|
@available(SwiftStdlib 6.2, *)
|
|
internal func _getCurrentTaskNameString() -> String? {
|
|
if let stringPtr = _getCurrentTaskName() {
|
|
unsafe String(cString: stringPtr)
|
|
} else {
|
|
nil
|
|
}
|
|
}
|
|
|
|
|
|
#if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
|
|
@available(SwiftStdlib 5.8, *)
|
|
@usableFromInline
|
|
@_unavailableFromAsync(message: "Use _taskRunInline from a sync context to begin an async context.")
|
|
internal func _taskRunInline<T>(_ body: () async -> T) -> T {
|
|
#if compiler(>=5.5) && $BuiltinTaskRunInline
|
|
return Builtin.taskRunInline(body)
|
|
#else
|
|
fatalError("Unsupported Swift compiler")
|
|
#endif
|
|
}
|
|
|
|
@available(SwiftStdlib 5.8, *)
|
|
extension Task where Failure == Never {
|
|
/// Start an async context within the current sync context and run the
|
|
/// provided async closure, returning the value it produces.
|
|
@available(SwiftStdlib 5.8, *)
|
|
@_spi(_TaskToThreadModel)
|
|
@_unavailableFromAsync(message: "Use Task.runInline from a sync context to begin an async context.")
|
|
public static func runInline(_ body: () async -> Success) -> Success {
|
|
return _taskRunInline(body)
|
|
}
|
|
}
|
|
|
|
@available(SwiftStdlib 5.8, *)
|
|
extension Task where Failure == Error {
|
|
@available(SwiftStdlib 5.8, *)
|
|
@_alwaysEmitIntoClient
|
|
@usableFromInline
|
|
internal static func _runInlineHelper<T>(
|
|
body: () async -> Result<T, Error>,
|
|
rescue: (Result<T, Error>) throws -> T
|
|
) rethrows -> T {
|
|
return try rescue(
|
|
_taskRunInline(body)
|
|
)
|
|
}
|
|
|
|
/// Start an async context within the current sync context and run the
|
|
/// provided async closure, returning or throwing the value or error it
|
|
/// produces.
|
|
@available(SwiftStdlib 5.8, *)
|
|
@_spi(_TaskToThreadModel)
|
|
@_unavailableFromAsync(message: "Use Task.runInline from a sync context to begin an async context.")
|
|
public static func runInline(_ body: () async throws -> Success) rethrows -> Success {
|
|
return try _runInlineHelper(
|
|
body: {
|
|
do {
|
|
let value = try await body()
|
|
return Result.success(value)
|
|
}
|
|
catch let error {
|
|
return Result.failure(error)
|
|
}
|
|
},
|
|
rescue: { try $0.get() }
|
|
)
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if _runtime(_ObjC)
|
|
|
|
#if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
|
|
/// Intrinsic used by SILGen to launch a task for bridging a Swift async method
|
|
/// which was called through its ObjC-exported completion-handler-based API.
|
|
@available(SwiftStdlib 5.1, *)
|
|
@_alwaysEmitIntoClient
|
|
@usableFromInline
|
|
internal func _runTaskForBridgedAsyncMethod(@_inheritActorContext _ body: __owned @Sendable @escaping () async -> Void) {
|
|
#if compiler(>=5.6)
|
|
Task(operation: body)
|
|
#else
|
|
Task<Int, Error> {
|
|
await body()
|
|
return 0
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
#endif
|
|
|