Files
swift-mirror/stdlib/public/Concurrency/Task.swift
John McCall 2012195cd5 Alter the runtime interface for awaiting futures and task groups.
First, just call an async -> T function instead of forcing the caller
to piece together which case we're in and perform its own copy.  This
ensures that the task is actually kept alive properly.

Second, now that we no longer implicitly depend on the waiting tasks
being run synchronously, go ahead and schedule them to run on the
global executor.

This solves some problems which were blocking the work on TLS-ifying
the task/executor state.
2021-02-21 23:48:13 -05:00

690 lines
24 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
// ==== Task -------------------------------------------------------------------
/// An asynchronous task (just "Task" hereafter) is the analogue of a thread for
/// asynchronous functions. All asynchronous functions run as part of some task.
///
/// A task can only be interacted with by code running "in" the task,
/// by invoking the appropriate context sensitive static functions which operate
/// on the "current" task. Because all such functions are `async` they can only
/// be invoked as part of an existing task, and therefore are guaranteed to be
/// effective.
///
/// A task's execution can be seen as a series of periods where the task was
/// running. Each such period ends at a suspension point or -- finally -- the
/// completion of the task.
///
/// These partial periods towards the task's completion are `PartialAsyncTask`.
/// Partial tasks are generally not interacted with by end-users directly,
/// unless implementing a scheduler.
public struct Task {
internal let _task: Builtin.NativeObject
// May only be created by the standard library.
internal init(_ task: Builtin.NativeObject) {
self._task = task
}
}
// ==== Current Task -----------------------------------------------------------
extension Task {
/// Returns 'current' `Task` instance, representing the task from within which
/// this function was called.
///
/// All functions available on the Task
// TODO: once we can have async properties land make this computed property
@available(*, deprecated, message: "Please use Builtin.getCurrentAsyncTask() or Task.__unsafeCurrentAsync() until this function becomes implemented.")
public static func current(file: StaticString = #file, line: UInt = #line) async -> Task {
fatalError("Task.current() is not implemented yet!", file: file, line: line)
Task.unsafeCurrent!.task // !-safe, guaranteed to have a Task available within an async function.
}
}
// ==== Task Priority ----------------------------------------------------------
extension Task {
/// Returns the `current` task's priority.
///
/// If no current `Task` is available, returns `Priority.default`.
///
/// - SeeAlso: `Task.Priority`
/// - SeeAlso: `Task.priority`
@available(*, deprecated, message: "Not implemented yet, until unsafeCurrent is ready. Please use Task.__unsafeCurrentAsync().priority instead.")
public static var currentPriority: Priority {
Task.unsafeCurrent?.priority ?? Priority.default
}
/// Returns the `current` task's priority.
///
/// If no current `Task` is available, returns `Priority.default`.
///
/// - SeeAlso: `Task.Priority`
/// - SeeAlso: `Task.currentPriority`
public var priority: Priority {
getJobFlags(_task).priority
}
/// Task priority may inform decisions an `Executor` makes about how and when
/// to schedule tasks submitted to it.
///
/// ### Priority scheduling
/// An executor MAY utilize priority information to attempt running higher
/// priority tasks first, and then continuing to serve lower priority tasks.
///
/// The exact semantics of how priority is treated are left up to each
/// platform and `Executor` implementation.
///
/// ### Priority inheritance
/// Child tasks automatically inherit their parent task's priority.
///
/// Detached tasks (created by `Task.runDetached`) DO NOT inherit task priority,
/// as they are "detached" from their parent tasks after all.
///
/// ### Priority elevation
/// In some situations the priority of a task must be elevated (or "escalated", "raised"):
///
/// - if a `Task` running on behalf of an actor, and a new higher-priority
/// task is enqueued to the actor, its current task must be temporarily
/// elevated to the priority of the enqueued task, in order to allow the new
/// task to be processed at--effectively-- the priority it was enqueued with.
/// - this DOES NOT affect `Task.currentPriority()`.
/// - if a task is created with a `Task.Handle`, and a higher-priority task
/// calls the `await handle.get()` function the priority of this task must be
/// permanently increased until the task completes.
/// - this DOES affect `Task.currentPriority()`.
///
/// TODO: Define the details of task priority; It is likely to be a concept
/// similar to Darwin Dispatch's QoS; bearing in mind that priority is not as
/// much of a thing on other platforms (i.e. server side Linux systems).
public enum Priority: Int, Comparable {
// Values must be same as defined by the internal `JobPriority`.
case userInteractive = 0x21
case userInitiated = 0x19
case `default` = 0x15
case utility = 0x11
case background = 0x09
case unspecified = 0x00
public static func < (lhs: Priority, rhs: Priority) -> Bool {
lhs.rawValue < rhs.rawValue
}
}
}
// ==== Task Handle ------------------------------------------------------------
extension Task {
/// A task handle refers to an in-flight `Task`,
/// allowing for potentially awaiting for its result or Cancelling it.
///
/// It is not a programming error to drop a handle without awaiting or cancelling it,
/// i.e. the task will run regardless of the handle still being present or not.
/// Dropping a handle however means losing the ability to await on the task's result
/// and losing the ability to cancel it.
public struct Handle<Success, Failure: Error> {
private let _task: Builtin.NativeObject
internal init(_ task: Builtin.NativeObject) {
self._task = task
}
/// Returns the `Task` that this handle refers to.
public var task: Task {
Task(_task)
}
/// Wait for the task to complete, returning (or throwing) its result.
///
/// ### Priority
/// If the task has not completed yet, its priority will be elevated to the
/// priority of the current task. Note that this may not be as effective as
/// creating the task with the "right" priority to in the first place.
///
/// ### Cancellation
/// If the awaited on task gets cancelled externally the `get()` will throw
/// a cancellation error.
///
/// If the task gets cancelled internally, e.g. by checking for cancellation
/// and throwing a specific error or using `checkCancellation` the error
/// thrown out of the task will be re-thrown here.
public func get() async throws -> Success {
return try await _taskFutureGetThrowing(_task)
}
/// Wait for the task to complete, returning its `Result`.
///
/// ### Priority
/// If the task has not completed yet, its priority will be elevated to the
/// priority of the current task. Note that this may not be as effective as
/// creating the task with the "right" priority to in the first place.
///
/// ### Cancellation
/// If the awaited on task gets cancelled externally the `get()` will throw
/// a cancellation error.
///
/// If the task gets cancelled internally, e.g. by checking for cancellation
/// and throwing a specific error or using `checkCancellation` the error
/// thrown out of the task will be re-thrown here.
public func getResult() async -> Result<Success, Failure> {
do {
return .success(try await get())
} catch {
return .failure(error as! Failure) // as!-safe, guaranteed to be Failure
}
}
/// Attempt to cancel the task.
///
/// Whether this function has any effect is task-dependent.
///
/// For a task to respect cancellation it must cooperatively check for it
/// while running. Many tasks will check for cancellation before beginning
/// their "actual work", however this is not a requirement nor is it guaranteed
/// how and when tasks check for cancellation in general.
public func cancel() {
Builtin.cancelAsyncTask(_task)
}
}
}
extension Task.Handle where Failure == Never {
/// Wait for the task to complete, returning its result.
///
/// ### Priority
/// If the task has not completed yet, its priority will be elevated to the
/// priority of the current task. Note that this may not be as effective as
/// creating the task with the "right" priority to in the first place.
///
/// ### Cancellation
/// The task this handle refers to may check for cancellation, however
/// since it is not-throwing it would have to handle it using some other
/// way than throwing a `CancellationError`, e.g. it could provide a neutral
/// value of the `Success` type, or encode that cancellation has occurred in
/// that type itself.
public func get() async -> Success {
return await _taskFutureGet(_task)
}
}
extension Task.Handle: Hashable {
public func hash(into hasher: inout Hasher) {
UnsafeRawPointer(Builtin.bridgeToRawPointer(_task)).hash(into: &hasher)
}
}
extension Task.Handle: Equatable {
public static func ==(lhs: Self, rhs: Self) -> Bool {
UnsafeRawPointer(Builtin.bridgeToRawPointer(lhs._task)) ==
UnsafeRawPointer(Builtin.bridgeToRawPointer(rhs._task))
}
}
// ==== Conformances -----------------------------------------------------------
extension Task: Hashable {
public func hash(into hasher: inout Hasher) {
UnsafeRawPointer(Builtin.bridgeToRawPointer(_task)).hash(into: &hasher)
}
}
extension Task: Equatable {
public static func ==(lhs: Self, rhs: Self) -> Bool {
UnsafeRawPointer(Builtin.bridgeToRawPointer(lhs._task)) ==
UnsafeRawPointer(Builtin.bridgeToRawPointer(rhs._task))
}
}
// ==== Job Flags --------------------------------------------------------------
extension Task {
/// Flags for schedulable jobs.
///
/// This is a port of the C++ FlagSet.
struct JobFlags {
/// Kinds of schedulable jobs.
enum Kind: Int {
case task = 0
};
/// The actual bit representation of these flags.
var bits: Int = 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: Priority {
get {
Priority(rawValue: (bits & 0xFF00) >> 8)!
}
set {
bits = (bits & ~0xFF00) | (newValue.rawValue << 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 task group.
var isTaskGroup: Bool {
get {
(bits & (1 << 26)) != 0
}
set {
if newValue {
bits = bits | 1 << 26
} else {
bits = (bits & ~(1 << 26))
}
}
}
/// Whether this (or its parents) have task local values.
var hasLocalValues: Bool {
get {
(bits & (1 << 27)) != 0
}
set {
if newValue {
bits = bits | 1 << 27
} else {
bits = (bits & ~(1 << 27))
}
}
}
}
}
// ==== Detached Tasks ---------------------------------------------------------
extension Task {
/// Run given throwing `operation` as part of a new top-level task.
///
/// Creating detached tasks should, generally, be avoided in favor of using
/// `async` functions, `async let` declarations and `await` expressions - as
/// those benefit from structured, bounded concurrency which is easier to reason
/// about, as well as automatically inheriting the parent tasks priority,
/// task-local storage, deadlines, as well as being cancelled automatically
/// when their parent task is cancelled. Detached tasks do not get any of those
/// benefits, and thus should only be used when an operation is impossible to
/// be modelled with child tasks.
///
/// ### Cancellation
/// A detached task always runs to completion unless it is explicitly cancelled.
/// Specifically, dropping a detached tasks `Task.Handle` does _not_ automatically
/// cancel given task.
///
/// Cancelling a task must be performed explicitly via `handle.cancel()`.
///
/// - Note: it is generally preferable to use child tasks rather than detached
/// tasks. Child tasks automatically carry priorities, task-local state,
/// deadlines and have other benefits resulting from the structured
/// concurrency concepts that they model. Consider using detached tasks only
/// when strictly necessary and impossible to model operations otherwise.
///
/// - Parameters:
/// - priority: priority of the task
/// - executor: the executor on which the detached closure should start
/// executing on.
/// - operation: the operation to execute
/// - Returns: handle to the task, allowing to `await handle.get()` on the
/// tasks result or `cancel` it. If the operation fails the handle will
/// throw the error the operation has thrown when awaited on.
@discardableResult
public static func runDetached<T>(
priority: Priority = .default,
startingOn executor: ExecutorRef? = nil,
operation: @concurrent @escaping () async -> T
// TODO: Allow inheriting task-locals?
) -> Handle<T, Never> {
assert(executor == nil, "Custom executor support is not implemented yet.") // FIXME
// Set up the job flags for a new task.
var flags = JobFlags()
flags.kind = .task
flags.priority = priority
flags.isFuture = true
// Create the asynchronous task future.
let (task, _) = Builtin.createAsyncTaskFuture(flags.bits, nil, operation)
// Enqueue the resulting job.
_enqueueJobGlobal(Builtin.convertTaskToJob(task))
return Handle<T, Never>(task)
}
/// Run given throwing `operation` as part of a new top-level task.
///
/// Creating detached tasks should, generally, be avoided in favor of using
/// `async` functions, `async let` declarations and `await` expressions - as
/// those benefit from structured, bounded concurrency which is easier to reason
/// about, as well as automatically inheriting the parent tasks priority,
/// task-local storage, deadlines, as well as being cancelled automatically
/// when their parent task is cancelled. Detached tasks do not get any of those
/// benefits, and thus should only be used when an operation is impossible to
/// be modelled with child tasks.
///
/// ### Cancellation
/// A detached task always runs to completion unless it is explicitly cancelled.
/// Specifically, dropping a detached tasks `Task.Handle` does _not_ automatically
/// cancel given task.
///
/// Cancelling a task must be performed explicitly via `handle.cancel()`.
///
/// - Note: it is generally preferable to use child tasks rather than detached
/// tasks. Child tasks automatically carry priorities, task-local state,
/// deadlines and have other benefits resulting from the structured
/// concurrency concepts that they model. Consider using detached tasks only
/// when strictly necessary and impossible to model operations otherwise.
///
/// - Parameters:
/// - priority: priority of the task
/// - executor: the executor on which the detached closure should start
/// executing on.
/// - operation: the operation to execute
/// - Returns: handle to the task, allowing to `await handle.get()` on the
/// tasks result or `cancel` it. If the operation fails the handle will
/// throw the error the operation has thrown when awaited on.
@discardableResult
public static func runDetached<T, Failure>(
priority: Priority = .default,
startingOn executor: ExecutorRef? = nil,
operation: @concurrent @escaping () async throws -> T
) -> Handle<T, Failure> {
assert(executor == nil, "Custom executor support is not implemented yet.") // FIXME
// Set up the job flags for a new task.
var flags = JobFlags()
flags.kind = .task
flags.priority = priority
flags.isFuture = true
// Create the asynchronous task future.
let (task, _) = Builtin.createAsyncTaskFuture(flags.bits, nil, operation)
// Enqueue the resulting job.
_enqueueJobGlobal(Builtin.convertTaskToJob(task))
return Handle<T, Failure>(task)
}
}
// ==== Async Handler ----------------------------------------------------------
public func _runAsyncHandler(operation: @escaping () async -> ()) {
typealias ConcurrentFunctionType = @concurrent () async -> ()
Task.runDetached(
operation: unsafeBitCast(operation, to: ConcurrentFunctionType.self)
)
}
// ==== Voluntary Suspension -----------------------------------------------------
extension Task {
/// Explicitly suspend the current task, potentially giving up execution actor
/// of current actor/task, allowing other tasks to execute.
///
/// This is not a perfect cure for starvation;
/// if the task is the highest-priority task in the system, it might go
/// immediately back to executing.
@available(*, deprecated, message: "Not implemented yet.")
public static func yield() async {
fatalError("\(#function) not implemented yet.")
}
}
// ==== UnsafeCurrentTask ------------------------------------------------------
extension Task {
/// If available, returns the 'current' task, representing the async context
/// from which this function was called.
///
/// This computed property can be called from 'async' as well as synchronous
/// functions. Within synchronous functions it may return a `nil` value,
/// which means that the function was not called within any asynchronous task
/// at all, otherwise the returned task is equal to the task of the nearest
/// asynchronous function present in this functions call stack.
///
/// The returned value must not be accessed from tasks other than the current one.
@available(*, deprecated, message: "Not implemented yet, use Builtin.getCurrentAsyncTask() or Task.___unsafeCurrentAsync() until this function is implemented.")
public static var unsafeCurrent: UnsafeCurrentTask? {
// FIXME: rdar://70546948 implement this once getCurrentAsyncTask can be called from sync funcs
// guard let _task = Builtin.getCurrentAsyncTask() else {
// return nil
// }
// return UnsafeCurrentTask(_task)
fatalError("\(#function) is not implemented yet")
}
@available(*, deprecated, message: "This will be removed, and replaced by unsafeCurrent().", renamed: "unsafeCurrent()")
public static func __unsafeCurrentAsync() async -> UnsafeCurrentTask {
let task = Builtin.getCurrentAsyncTask()
_swiftRetain(task)
return UnsafeCurrentTask(task)
}
}
/// An *unsafe* 'current' task handle.
///
/// An `UnsafeCurrentTask` should not be stored for "later" access.
///
/// Storing an `UnsafeCurrentTask` has no implication on the task's actual lifecycle.
///
/// The sub-set of APIs of `UnsafeCurrentTask` which also exist on `Task` are
/// generally safe to be invoked from any task/thread.
///
/// All other APIs must not, be called 'from' any other task than the one
/// represented by this handle itself. Doing so may result in undefined behavior,
/// and most certainly will break invariants in other places of the program
/// actively running on this task.
public struct UnsafeCurrentTask {
private let _task: Builtin.NativeObject
// May only be created by the standard library.
internal init(_ task: Builtin.NativeObject) {
self._task = task
}
/// Returns `Task` representing the same asynchronous context as this 'UnsafeCurrentTask'.
///
/// Operations on `Task` (unlike `UnsafeCurrentTask`) are safe to be called
/// from any other task (or thread).
public var task: Task {
Task(_task)
}
/// Returns `true` if the task is cancelled, and should stop executing.
///
/// - SeeAlso: `checkCancellation()`
public var isCancelled: Bool {
_taskIsCancelled(_task)
}
/// Returns the `current` task's priority.
///
/// If no current `Task` is available, returns `Priority.default`.
///
/// - SeeAlso: `Task.Priority`
/// - SeeAlso: `Task.currentPriority`
public var priority: Task.Priority {
getJobFlags(_task).priority
}
}
// ==== Internal ---------------------------------------------------------------
@_silgen_name("swift_task_getJobFlags")
func getJobFlags(_ task: Builtin.NativeObject) -> Task.JobFlags
@_silgen_name("swift_task_enqueueGlobal")
@usableFromInline
func _enqueueJobGlobal(_ task: Builtin.Job)
@available(*, deprecated)
@_silgen_name("swift_task_runAndBlockThread")
public func runAsyncAndBlock(_ asyncFun: @escaping () async -> ())
@_silgen_name("swift_task_asyncMainDrainQueue")
public func _asyncMainDrainQueue() -> Never
public func _runAsyncMain(_ asyncFun: @escaping () async throws -> ()) {
Task.runDetached {
do {
try await asyncFun()
exit(0)
} catch {
_errorInMain(error)
}
}
_asyncMainDrainQueue()
}
// 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
@_silgen_name("swift_task_future_wait")
public func _taskFutureGet<T>(_ task: Builtin.NativeObject) async -> T
@_silgen_name("swift_task_future_wait_throwing")
public func _taskFutureGetThrowing<T>(_ task: Builtin.NativeObject) async throws -> T
public func _runChildTask<T>(
operation: @concurrent @escaping () async throws -> T
) async -> Builtin.NativeObject {
let currentTask = Builtin.getCurrentAsyncTask()
// Set up the job flags for a new task.
var flags = Task.JobFlags()
flags.kind = .task
flags.priority = getJobFlags(currentTask).priority
flags.isFuture = true
flags.isChildTask = true
// Create the asynchronous task future.
let (task, _) = Builtin.createAsyncTaskFuture(
flags.bits, currentTask, operation)
// Enqueue the resulting job.
_enqueueJobGlobal(Builtin.convertTaskToJob(task))
return task
}
class StringLike: CustomStringConvertible {
let value: String
init(_ value: String) {
self.value = value
}
var description: String { value }
}
public func _runGroupChildTask<T>(
overridingPriority priorityOverride: Task.Priority? = nil,
withLocalValues hasLocalValues: Bool = false,
operation: @concurrent @escaping () async throws -> T
) async -> Builtin.NativeObject {
let currentTask = Builtin.getCurrentAsyncTask()
// Set up the job flags for a new task.
var flags = Task.JobFlags()
flags.kind = .task
flags.priority = priorityOverride ?? getJobFlags(currentTask).priority
flags.isFuture = true
flags.isChildTask = true
// Create the asynchronous task future.
let (task, _) = Builtin.createAsyncTaskFuture(
flags.bits, currentTask, operation)
// Enqueue the resulting job.
_enqueueJobGlobal(Builtin.convertTaskToJob(task))
return task
}
@_silgen_name("swift_task_cancel")
func _taskCancel(_ task: Builtin.NativeObject)
@_silgen_name("swift_task_isCancelled")
func _taskIsCancelled(_ task: Builtin.NativeObject) -> Bool
#if _runtime(_ObjC)
/// 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.
@_alwaysEmitIntoClient
@usableFromInline
internal func _runTaskForBridgedAsyncMethod(_ body: @escaping () async -> Void) {
// TODO: We can probably do better than Task.runDetached
// if we're already running on behalf of a task,
// if the receiver of the method invocation is itself an Actor, or in other
// situations.
Task.runDetached { await body() }
}
#endif