Skip to content

Commit b6e07a9

Browse files
authored
Merge pull request #34391 from ktoso/wip-concurrency-tasks
[Concurrency] Add API stubs for Task
2 parents 08612ff + b5fd2a5 commit b6e07a9

File tree

5 files changed

+330
-0
lines changed

5 files changed

+330
-0
lines changed

stdlib/public/Concurrency/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ add_swift_target_library(swift_Concurrency ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} I
1414
Actor.swift
1515
PartialAsyncTask.swift
1616
Task.cpp
17+
Task.swift
1718
TaskAlloc.cpp
1819
TaskStatus.cpp
1920
Mutex.cpp
@@ -36,6 +37,7 @@ add_swift_target_library(swift_Concurrency ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} I
3637
SWIFT_COMPILE_FLAGS
3738
${SWIFT_STANDARD_LIBRARY_SWIFT_FLAGS}
3839
-parse-stdlib
40+
-Xfrontend -enable-experimental-concurrency
3941
LINK_FLAGS "${SWIFT_RUNTIME_SWIFT_LINK_FLAGS}"
4042
INSTALL_IN_COMPONENT stdlib
4143
)

stdlib/public/Concurrency/PartialAsyncTask.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import Swift
1414
@_implementationOnly import _SwiftConcurrencyShims
1515

16+
/// A partial task is a unit of scheduleable work.
1617
public struct PartialAsyncTask {
1718
private var context: UnsafeMutablePointer<_SwiftContext>
1819

stdlib/public/Concurrency/Task.swift

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
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+
}

test/Concurrency/actor_isolation.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ var mutableGlobal: String = "can't touch this" // expected-note 2{{var declared
77
func globalFunc() { }
88
func acceptClosure<T>(_: () -> T) { }
99
func acceptEscapingClosure<T>(_: @escaping () -> T) { }
10+
func acceptEscapingClosure<T>(_: (String) -> ()) async -> T? { nil }
1011

1112
func acceptAsyncClosure<T>(_: () async -> T) { }
1213
func acceptEscapingAsyncClosure<T>(_: @escaping () async -> T) { }
1314

15+
1416
// ----------------------------------------------------------------------
1517
// Actor state isolation restrictions
1618
// ----------------------------------------------------------------------

test/Concurrency/async_tasks.swift

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// RUN: %target-typecheck-verify-swift -enable-experimental-concurrency
2+
// REQUIRES: concurrency
3+
4+
func someAsyncFunc() async -> String { "" }
5+
6+
struct MyError: Error {}
7+
func someThrowingAsyncFunc() async throws -> String { throw MyError() }
8+
9+
func test_unsafeContinuations() async {
10+
// the closure should not allow async operations;
11+
// after all: if you have async code, just call it directly, without the unsafe continuation
12+
let _: String = Task.withUnsafeContinuation { continuation in // expected-error{{cannot convert value of type '(_) async -> ()' to expected argument type '(Task.UnsafeContinuation<String>) -> Void'}}
13+
let s = await someAsyncFunc() // rdar://70610141 for getting a better error message here
14+
continuation.resume(returning: s)
15+
}
16+
17+
let _: String = await Task.withUnsafeContinuation { continuation in
18+
continuation.resume(returning: "")
19+
}
20+
}
21+
22+
func test_unsafeThrowingContinuations() async {
23+
let _: String = try await Task.withUnsafeThrowingContinuation { continuation in
24+
continuation.resume(returning: "")
25+
}
26+
27+
let _: String = try await Task.withUnsafeThrowingContinuation { continuation in
28+
continuation.resume(throwing: MyError())
29+
}
30+
31+
// TODO: Potentially could offer some warnings if we know that a continuation was resumed or escaped at all in a closure?
32+
}
33+
34+
// ==== Detached Tasks ---------------------------------------------------------
35+
36+
func test_detached() async throws {
37+
let handle = Task.runDetached() {
38+
await someAsyncFunc() // able to call async functions
39+
}
40+
41+
let result: String = await try handle.get()
42+
_ = result
43+
}
44+
45+
func test_detached_throwing() async -> String {
46+
let handle: Task.Handle<String, Error> = Task.runDetached() {
47+
await try someThrowingAsyncFunc() // able to call async functions
48+
}
49+
50+
do {
51+
return await try handle.get()
52+
} catch {
53+
print("caught: \(error)")
54+
}
55+
}
56+
57+
// ==== Current Task -----------------------------------------------------------
58+
59+
func test_current_task() async {
60+
_ = await Task.current() // yay, we know "in" what task we're executing
61+
}

0 commit comments

Comments
 (0)