|
| 1 | +////===----------------------------------------------------------------------===// |
| 2 | +//// |
| 3 | +//// This source file is part of the Swift.org open source project |
| 4 | +//// |
| 5 | +//// Copyright (c) 2020 Apple Inc. and the Swift project authors |
| 6 | +//// Licensed under Apache License v2.0 with Runtime Library Exception |
| 7 | +//// |
| 8 | +//// See https://swift.org/LICENSE.txt for license information |
| 9 | +//// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| 10 | +//// |
| 11 | +////===----------------------------------------------------------------------===// |
| 12 | + |
| 13 | +import Swift |
| 14 | +@_implementationOnly import _SwiftConcurrencyShims |
| 15 | + |
| 16 | +// ==== Task ------------------------------------------------------------------- |
| 17 | + |
| 18 | +/// An asynchronous task (just "Task" hereafter) is the analogue of a thread for |
| 19 | +/// asynchronous functions. All asynchronous functions run as part of some task. |
| 20 | +/// |
| 21 | +/// A task's execution can be seen as a series of periods where the task was |
| 22 | +/// running. Each such period ends at a suspension point or -- finally -- the |
| 23 | +/// completion of the task. |
| 24 | +/// |
| 25 | +/// These partial periods towards the task's completion are `PartialAsyncTask`. |
| 26 | +/// Partial tasks are generally not interacted with by end-users directly, |
| 27 | +/// unless implementing a scheduler. |
| 28 | +public struct Task { |
| 29 | +} |
| 30 | + |
| 31 | +// ==== Current Task ----------------------------------------------------------- |
| 32 | + |
| 33 | +extension Task { |
| 34 | + /// Returns the currently executing `Task`. |
| 35 | + /// |
| 36 | + /// As invoking this function is only possible from an asynchronous context |
| 37 | + /// it is always able to return the current `Task` in which we are currently |
| 38 | + /// running. |
| 39 | + public static func current() async -> Task { |
| 40 | + fatalError("\(#function) not implemented yet.") // TODO: needs a built-in function |
| 41 | + } |
| 42 | +} |
| 43 | + |
| 44 | +// ==== Task Priority ---------------------------------------------------------- |
| 45 | + |
| 46 | +extension Task { |
| 47 | + /// Task priority may inform decisions an `Executor` makes about how and when |
| 48 | + /// to schedule tasks submitted to it. |
| 49 | + /// |
| 50 | + /// ### Priority scheduling |
| 51 | + /// An executor MAY utilize priority information to attempt running higher |
| 52 | + /// priority tasks first, and then continuing to serve lower priority tasks. |
| 53 | + /// |
| 54 | + /// The exact semantics of how priority is treated are left up to each |
| 55 | + /// platform and `Executor` implementation. |
| 56 | + /// |
| 57 | + /// ### Priority inheritance |
| 58 | + /// Child tasks automatically inherit their parent task's priority. |
| 59 | + /// |
| 60 | + /// Detached tasks (created by `Task.runDetached`) DO NOT inherit task priority, |
| 61 | + /// as they are "detached" from their parent tasks after all. |
| 62 | + /// |
| 63 | + /// ### Priority elevation |
| 64 | + /// In some situations the priority of a task must be elevated ("raised"): |
| 65 | + /// |
| 66 | + /// - if a `Task` running on behalf of an actor, and a new higher-priority |
| 67 | + /// task is enqueued to the actor, its current task must be temporarily |
| 68 | + /// elevated to the priority of the enqueued task, in order to allow the new |
| 69 | + /// task to be processed at--effectively-- the priority it was enqueued with. |
| 70 | + /// - this DOES NOT affect `Task.currentPriority()`. |
| 71 | + /// - if a task is created with a `Task.Handle`, and a higher-priority task |
| 72 | + /// calls the `await handle.get()` function the priority of this task must be |
| 73 | + /// permanently increased until the task completes. |
| 74 | + /// - this DOES affect `Task.currentPriority()`. |
| 75 | + /// |
| 76 | + /// TODO: Define the details of task priority; It is likely to be a concept |
| 77 | + /// similar to Darwin Dispatch's QoS; bearing in mind that priority is not as |
| 78 | + /// much of a thing on other platforms (i.e. server side Linux systems). |
| 79 | + public struct Priority: Comparable { |
| 80 | + public static let `default`: Task.Priority = .init() // TODO: replace with actual values |
| 81 | + |
| 82 | + // TODO: specifics of implementation are not decided yet |
| 83 | + private let __value: Int = 0 |
| 84 | + |
| 85 | + public static func < (lhs: Self, rhs: Self) -> Bool { |
| 86 | + lhs.__value < rhs.__value |
| 87 | + } |
| 88 | + } |
| 89 | +} |
| 90 | + |
| 91 | +// ==== Task Handle ------------------------------------------------------------ |
| 92 | + |
| 93 | +extension Task { |
| 94 | + |
| 95 | + /// A task handle refers to an in-flight `Task`, |
| 96 | + /// allowing for potentially awaiting for its result or canceling it. |
| 97 | + /// |
| 98 | + /// It is not a programming error to drop a handle without awaiting or canceling it, |
| 99 | + /// i.e. the task will run regardless of the handle still being present or not. |
| 100 | + /// Dropping a handle however means losing the ability to await on the task's result |
| 101 | + /// and losing the ability to cancel it. |
| 102 | + public final class Handle<Success, Failure: Error> { |
| 103 | + /// Wait for the task to complete, returning (or throwing) its result. |
| 104 | + /// |
| 105 | + /// ### Priority |
| 106 | + /// If the task has not completed yet, its priority will be elevated to the |
| 107 | + /// priority of the current task. Note that this may not be as effective as |
| 108 | + /// creating the task with the "right" priority to in the first place. |
| 109 | + /// |
| 110 | + /// ### Cancellation |
| 111 | + /// If the awaited on task gets cancelled the `get()` will throw a cancellation error. |
| 112 | + public func get() async throws -> Success { |
| 113 | + fatalError("\(#function) not implemented yet.") |
| 114 | + } |
| 115 | + |
| 116 | + /// Attempt to cancel the task. |
| 117 | + /// |
| 118 | + /// Whether this function has any effect is task-dependent. |
| 119 | + /// |
| 120 | + /// For a task to respect cancellation it must cooperatively check for it |
| 121 | + /// while running. Many tasks will check for cancellation before beginning |
| 122 | + /// their "actual work", however this is not a requirement nor is it guaranteed |
| 123 | + /// how and when tasks check for cancellation in general. |
| 124 | + public func cancel() { |
| 125 | + fatalError("\(#function) not implemented yet.") |
| 126 | + } |
| 127 | + } |
| 128 | +} |
| 129 | + |
| 130 | +// ==== Detached Tasks --------------------------------------------------------- |
| 131 | + |
| 132 | +extension Task { |
| 133 | + /// Run given `operation` as part of a new top-level task. |
| 134 | + /// |
| 135 | + /// Creating detached tasks should, generally, be avoided in favor of using |
| 136 | + /// `async` functions, `async let` declarations and `await` expressions - as |
| 137 | + /// those benefit from structured, bounded concurrency which is easier to reason |
| 138 | + /// about, as well as automatically inheriting the parent tasks priority, |
| 139 | + /// task-local storage, deadlines, as well as being cancelled automatically |
| 140 | + /// when their parent task is cancelled. Detached tasks do not get any of those |
| 141 | + /// benefits, and thus should only be used when an operation is impossible to |
| 142 | + /// be modelled with child tasks. |
| 143 | + /// |
| 144 | + /// ### Cancellation |
| 145 | + /// A detached task always runs to completion unless it is explicitly cancelled. |
| 146 | + /// Specifically, dropping a detached tasks `Task.Handle` does _not_ automatically |
| 147 | + /// cancel given task. |
| 148 | + /// |
| 149 | + /// Canceling a task must be performed explicitly via `handle.cancel()`. |
| 150 | + /// |
| 151 | + /// - Parameters: |
| 152 | + /// - priority: priority of the task TODO: reword and define more explicitly once we have priorities well-defined |
| 153 | + /// - operation: |
| 154 | + /// - Returns: handle to the task, allowing to `await handle.get()` on the |
| 155 | + /// tasks result or `cancel` it. |
| 156 | + /// |
| 157 | + /// - Note: it is generally preferable to use child tasks rather than detached |
| 158 | + /// tasks. Child tasks automatically carry priorities, task-local state, |
| 159 | + /// deadlines and have other benefits resulting from the structured |
| 160 | + /// concurrency concepts that they model. Consider using detached tasks only |
| 161 | + /// when strictly necessary and impossible to model operations otherwise. |
| 162 | + public static func runDetached<T>( |
| 163 | + priority: Priority = .default, |
| 164 | + operation: () async -> T |
| 165 | + ) -> Handle<T, Never> { |
| 166 | + fatalError("\(#function) not implemented yet.") |
| 167 | + } |
| 168 | + |
| 169 | + /// Run given throwing `operation` as part of a new top-level task. |
| 170 | + /// |
| 171 | + /// Creating detached tasks should, generally, be avoided in favor of using |
| 172 | + /// `async` functions, `async let` declarations and `await` expressions - as |
| 173 | + /// those benefit from structured, bounded concurrency which is easier to reason |
| 174 | + /// about, as well as automatically inheriting the parent tasks priority, |
| 175 | + /// task-local storage, deadlines, as well as being cancelled automatically |
| 176 | + /// when their parent task is cancelled. Detached tasks do not get any of those |
| 177 | + /// benefits, and thus should only be used when an operation is impossible to |
| 178 | + /// be modelled with child tasks. |
| 179 | + /// |
| 180 | + /// ### Cancellation |
| 181 | + /// A detached task always runs to completion unless it is explicitly cancelled. |
| 182 | + /// Specifically, dropping a detached tasks `Task.Handle` does _not_ automatically |
| 183 | + /// cancel given task. |
| 184 | + /// |
| 185 | + /// Canceling a task must be performed explicitly via `handle.cancel()`. |
| 186 | + /// |
| 187 | + /// - Parameters: |
| 188 | + /// - priority: priority of the task TODO: reword and define more explicitly once we have priorities well-defined |
| 189 | + /// - operation: |
| 190 | + /// - Returns: handle to the task, allowing to `await handle.get()` on the |
| 191 | + /// tasks result or `cancel` it. If the operation fails the handle will |
| 192 | + /// throw the error the operation has thrown when awaited on. |
| 193 | + /// |
| 194 | + /// - Note: it is generally preferable to use child tasks rather than detached |
| 195 | + /// tasks. Child tasks automatically carry priorities, task-local state, |
| 196 | + /// deadlines and have other benefits resulting from the structured |
| 197 | + /// concurrency concepts that they model. Consider using detached tasks only |
| 198 | + /// when strictly necessary and impossible to model operations otherwise. |
| 199 | + public static func runDetached<T>( |
| 200 | + priority: Priority = .default, |
| 201 | + operation: () async throws -> T |
| 202 | + ) -> Handle<T, Error> { |
| 203 | + fatalError("\(#function) not implemented yet.") |
| 204 | + } |
| 205 | +} |
| 206 | + |
| 207 | +// ==== UnsafeContinuation ----------------------------------------------------- |
| 208 | + |
| 209 | +extension Task { |
| 210 | + public struct UnsafeContinuation<T> { |
| 211 | + /// Return a value into the continuation and make the task schedulable. |
| 212 | + /// |
| 213 | + /// The task will never run synchronously, even if the task does not |
| 214 | + /// need to be resumed on a specific executor. |
| 215 | + /// |
| 216 | + /// This is appropriate when the caller is something "busy", like an event |
| 217 | + /// loop, and doesn't want to be potentially delayed by arbitrary work. |
| 218 | + public func resume(returning: T) { |
| 219 | + fatalError("\(#function) not implemented yet.") |
| 220 | + } |
| 221 | + } |
| 222 | + |
| 223 | + public struct UnsafeThrowingContinuation<T, E: Error> { |
| 224 | + /// Return a value into the continuation and make the task schedulable. |
| 225 | + /// |
| 226 | + /// The task will never run synchronously, even if the task does not |
| 227 | + /// need to be resumed on a specific executor. |
| 228 | + /// |
| 229 | + /// This is appropriate when the caller is something "busy", like an event |
| 230 | + /// loop, and doesn't want to be potentially delayed by arbitrary work. |
| 231 | + public func resume(returning: T) { |
| 232 | + fatalError("\(#function) not implemented yet.") |
| 233 | + } |
| 234 | + |
| 235 | + /// Resume the continuation with an error and make the task schedulable. |
| 236 | + /// |
| 237 | + /// The task will never run synchronously, even if the task does not |
| 238 | + /// need to be resumed on a specific executor. |
| 239 | + /// |
| 240 | + /// This is appropriate when the caller is something "busy", like an event |
| 241 | + /// loop, and doesn't want to be potentially delayed by arbitrary work. |
| 242 | + public func resume(throwing: E) { |
| 243 | + fatalError("\(#function) not implemented yet.") |
| 244 | + } |
| 245 | + } |
| 246 | + |
| 247 | + /// The operation functions must resume the continuation *exactly once*. |
| 248 | + /// |
| 249 | + /// The continuation will not begin executing until the operation function returns. |
| 250 | + public static func withUnsafeContinuation<T>( |
| 251 | + operation: (UnsafeContinuation<T>) -> Void |
| 252 | + ) async -> T { |
| 253 | + fatalError("\(#function) not implemented yet.") |
| 254 | + } |
| 255 | + |
| 256 | + /// The operation functions must resume the continuation *exactly once*. |
| 257 | + /// |
| 258 | + /// The continuation will not begin executing until the operation function returns. |
| 259 | + public static func withUnsafeThrowingContinuation<T>( |
| 260 | + operation: (UnsafeThrowingContinuation<T, Error>) -> Void |
| 261 | + ) async throws -> T { |
| 262 | + fatalError("\(#function) not implemented yet.") |
| 263 | + } |
| 264 | +} |
0 commit comments