blob: 0c95cc5529a85ad26210e252a4276e0b37378e27 [file] [log] [blame]
////===----------------------------------------------------------------------===//
////
//// 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 enum Task {
}
// ==== Task Priority ----------------------------------------------------------
extension Task {
/// Returns the current task's priority.
///
/// ### Suspension
/// This function returns instantly and will never suspend.
/* @instantaneous */
public static func currentPriority() async -> Priority {
let flags = getJobFlags(Builtin.getCurrentAsyncTask())
return flags.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 canceling it.
///
/// It is not a programming error to drop a handle without awaiting or canceling 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> {
let task: Builtin.NativeObject
/// 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 await try _taskFutureGetThrowing(task)
}
/// 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)
}
}
}
// ==== 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))
}
}
}
}
}
// ==== Detached Tasks ---------------------------------------------------------
extension Task {
/// Run given `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.
///
/// Canceling 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 TODO: reword and define more explicitly once we have priorities well-defined
/// - operation: the operation to execute
/// - Returns: handle to the task, allowing to `await handle.get()` on the
/// tasks result or `cancel` it.
public static func runDetached<T>(
priority: Priority = .default,
operation: @escaping () async -> T
) -> Handle<T> {
// 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>(task: 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.
///
/// Canceling 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 TODO: reword and define more explicitly once we have priorities well-defined
/// - 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.
public static func runDetached<T>(
priority: Priority = .default,
operation: @escaping () async throws -> T
) -> Handle<T> {
// 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>(task: task)
}
}
public func _runAsyncHandler(operation: @escaping () async -> ()) {
_ = Task.runDetached(operation: operation)
}
// ==== Voluntary Suspension -----------------------------------------------------
extension Task {
/// Suspend until a given point in time.
///
/// ### Cancellation
/// Does not check for cancellation and suspends the current context until the
/// given deadline.
///
/// - Parameter until: point in time until which to suspend.
public static func sleep(until: Deadline) async {
fatalError("\(#function) not implemented yet.")
}
/// 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.
public static func yield() async {
fatalError("\(#function) not implemented yet.")
}
}
@_silgen_name("swift_task_getJobFlags")
func getJobFlags(_ task: Builtin.NativeObject) -> Task.JobFlags
@_silgen_name("swift_task_enqueueGlobal")
@usableFromInline
func _enqueueJobGlobal(_ task: Builtin.Job)
@_silgen_name("swift_task_runAndBlockThread")
public func runAsyncAndBlock(_ asyncFun: @escaping () async -> ())
@_silgen_name("swift_task_future_wait")
func _taskFutureWait(
on task: Builtin.NativeObject
) async -> (hadErrorResult: Bool, storage: UnsafeRawPointer)
public func _taskFutureGet<T>(_ task: Builtin.NativeObject) async -> T {
let rawResult = await _taskFutureWait(on: task)
assert(!rawResult.hadErrorResult)
// Take the value.
let storagePtr =
rawResult.storage.bindMemory(to: T.self, capacity: 1)
return UnsafeMutablePointer<T>(mutating: storagePtr).pointee
}
public func _taskFutureGetThrowing<T>(
_ task: Builtin.NativeObject
) async throws -> T {
let rawResult = await _taskFutureWait(on: task)
if rawResult.hadErrorResult {
// Throw the result on error.
throw unsafeBitCast(rawResult.storage, to: Error.self)
}
// Take the value on success
let storagePtr =
rawResult.storage.bindMemory(to: T.self, capacity: 1)
return UnsafeMutablePointer<T>(mutating: storagePtr).pointee
}
public func _runChildTask<T>(operation: @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
}
#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