Skip to content

Commit e7cfc8c

Browse files
committed
[Concurency] Improve priority testing, including multi-task escalation
1 parent 08de933 commit e7cfc8c

File tree

3 files changed

+99
-40
lines changed

3 files changed

+99
-40
lines changed

stdlib/public/Concurrency/Task.swift

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@
1010
////
1111
////===----------------------------------------------------------------------===//
1212

13+
import Dispatch
14+
#if canImport(Darwin)
15+
import Darwin
16+
#elseif canImport(Glibc)
17+
import Glibc
18+
#elseif os(Windows)
19+
import CRT
20+
#endif
21+
22+
1323
import Swift
1424
@_implementationOnly import _SwiftConcurrencyShims
1525

@@ -31,7 +41,7 @@ import Swift
3141
/// These partial periods towards the task's completion are `PartialAsyncTask`.
3242
/// Partial tasks are generally not interacted with by end-users directly,
3343
/// unless implementing a scheduler.
34-
public struct Task: TaskOperations {
44+
public struct Task {
3545
internal let _task: Builtin.NativeObject
3646

3747
// May only be created by the standard library.
@@ -69,7 +79,12 @@ extension Task {
6979
Task.unsafeCurrent?.priority ?? Priority.default
7080
}
7181

72-
// Docs inherited from `TaskOperations`.
82+
/// Returns the `current` task's priority.
83+
///
84+
/// If no current `Task` is available, returns `Priority.default`.
85+
///
86+
/// - SeeAlso: `Task.Priority`
87+
/// - SeeAlso: `Task.currentPriority`
7388
public var priority: Priority {
7489
getJobFlags(_task).priority
7590
}
@@ -220,51 +235,29 @@ extension Task.Handle where Failure == Never {
220235

221236
extension Task.Handle: Hashable {
222237
public func hash(into hasher: inout Hasher) {
223-
unsafeBitCast(_task, to: UInt64.self).hash(into: &hasher)
238+
unsafeBitCast(_task, to: size_t.self).hash(into: &hasher)
224239
}
225240
}
226241

227242
extension Task.Handle: Equatable {
228243
public static func ==(lhs: Self, rhs: Self) -> Bool {
229-
unsafeBitCast(lhs._task, to: UInt64.self) ==
230-
unsafeBitCast(rhs._task, to: UInt64.self)
244+
unsafeBitCast(lhs._task, to: size_t.self) ==
245+
unsafeBitCast(rhs._task, to: size_t.self)
231246
}
232247
}
233248

234249
// ==== Conformances -----------------------------------------------------------
235250

236-
/// Protocol for those operations which are safe to be invoked on any `Task`
237-
/// instance regardless if the caller is running in the same task or a different
238-
/// one.
239-
///
240-
/// This protocol also lists the functions and computed properties shared between
241-
/// `Task` and `UnsafeCurrentTask`.
242-
public protocol TaskOperations {
243-
244-
/// Returns `true` if the task is cancelled, and should stop executing.
245-
///
246-
/// - SeeAlso: `checkCancellation()`
247-
var isCancelled: Bool { get }
248-
249-
/// Returns the `current` task's priority.
250-
///
251-
/// If no current `Task` is available, returns `Priority.default`.
252-
///
253-
/// - SeeAlso: `Task.Priority`
254-
/// - SeeAlso: `Task.currentPriority`
255-
var priority: Task.Priority { get }
256-
}
257-
258251
extension Task: Hashable {
259252
public func hash(into hasher: inout Hasher) {
260-
unsafeBitCast(_task, to: UInt64.self).hash(into: &hasher)
253+
unsafeBitCast(_task, to: size_t.self).hash(into: &hasher)
261254
}
262255
}
263256

264257
extension Task: Equatable {
265258
public static func ==(lhs: Self, rhs: Self) -> Bool {
266-
unsafeBitCast(lhs._task, to: UInt64.self) ==
267-
unsafeBitCast(rhs._task, to: UInt64.self)
259+
unsafeBitCast(lhs._task, to: size_t.self) ==
260+
unsafeBitCast(rhs._task, to: size_t.self)
268261
}
269262
}
270263

@@ -502,7 +495,7 @@ extension Task {
502495
///
503496
/// The returned value must not be accessed from tasks other than the current one.
504497
public static var unsafeCurrent: UnsafeCurrentTask? {
505-
// FIXME: implement this once getCurrentAsyncTask can be called from sync funcs
498+
// FIXME: rdar://70546948 implement this once getCurrentAsyncTask can be called from sync funcs
506499
// guard let _task = Builtin.getCurrentAsyncTask() else {
507500
// return nil
508501
// }
@@ -531,7 +524,7 @@ extension Task {
531524
/// represented by this handle itself. Doing so may result in undefined behavior,
532525
/// and most certainly will break invariants in other places of the program
533526
/// actively running on this task.
534-
public struct UnsafeCurrentTask: TaskOperations {
527+
public struct UnsafeCurrentTask {
535528
private let _task: Builtin.NativeObject
536529

537530
// May only be created by the standard library.
@@ -547,12 +540,19 @@ public struct UnsafeCurrentTask: TaskOperations {
547540
Task(_task)
548541
}
549542

550-
// Docs inherited from `TaskOperations`.
543+
/// Returns `true` if the task is cancelled, and should stop executing.
544+
///
545+
/// - SeeAlso: `checkCancellation()`
551546
public var isCancelled: Bool {
552547
_taskIsCancelled(_task)
553548
}
554549

555-
// Docs inherited from `TaskOperations`.
550+
/// Returns the `current` task's priority.
551+
///
552+
/// If no current `Task` is available, returns `Priority.default`.
553+
///
554+
/// - SeeAlso: `Task.Priority`
555+
/// - SeeAlso: `Task.currentPriority`
556556
public var priority: Task.Priority {
557557
getJobFlags(_task).priority
558558
}
@@ -572,7 +572,7 @@ func _enqueueJobGlobal(_ task: Builtin.Job)
572572
func isTaskCancelled(_ task: Builtin.NativeObject) -> Bool
573573

574574
@_silgen_name("swift_task_runAndBlockThread")
575-
func runAsyncAndBlock(_ asyncFun: @escaping () async -> ())
575+
public func runAsyncAndBlock(_ asyncFun: @escaping () async -> ())
576576

577577
@_silgen_name("swift_task_asyncMainDrainQueue")
578578
public func _asyncMainDrainQueue() -> Never

stdlib/public/Concurrency/TaskCancellation.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ extension Task {
2727
Task.unsafeCurrent?.isCancelled ?? false
2828
}
2929

30-
// docs inherited from protocol
30+
/// Returns `true` if the task is cancelled, and should stop executing.
31+
///
32+
/// - SeeAlso: `checkCancellation()`
3133
public var isCancelled: Bool {
3234
_taskIsCancelled(_task)
3335
}
Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,68 @@
1-
// RUN: %target-run-simple-swift(-Xfrontend -enable-experimental-concurrency -parse-as-library) | %FileCheck %s
1+
// RUN: %target-run-simple-swift(-Xfrontend -enable-experimental-concurrency -parse-as-library) | %FileCheck --dump-input=always %s
22
// REQUIRES: executable_test
33
// REQUIRES: concurrency
44

5+
#if canImport(Darwin)
6+
import Darwin
7+
#elseif canImport(Glibc)
8+
import Glibc
9+
#elseif os(Windows)
10+
import CRT
11+
#else
12+
#error("Unsupported platform")
13+
#endif
14+
15+
// FIXME: use `Task.currentPriority` once unsafeCurrent works in all these
16+
17+
func test_detach() async {
18+
let a1 = await Task.unsafeCurrentASYNC().task.priority
19+
print("a1: \(a1)") // CHECK: a1: default
20+
21+
// Note: remember to detach using a higher priority, otherwise a lower one
22+
// might be escalated by the get() and we could see `default` in the detached
23+
// task.
24+
await Task.runDetached(priority: .userInitiated) {
25+
let a2 = await Task.unsafeCurrentASYNC().task.priority
26+
print("a2: \(a2)") // CHECK: a2: userInitiated
27+
}.get()
28+
29+
let a3 = await Task.unsafeCurrentASYNC().task.priority
30+
print("a3: \(a3)") // CHECK: a3: default
31+
}
32+
33+
func test_multiple_lo_indirectly_escalated() async {
34+
func loopUntil(priority: Task.Priority) async {
35+
while (await Task.unsafeCurrentASYNC().task.priority != priority) {
36+
sleep(1)
37+
}
38+
}
39+
40+
let z = Task.runDetached(priority: .background) {
41+
await loopUntil(priority: .userInitiated)
42+
}
43+
let x = Task.runDetached(priority: .background) {
44+
_ = await z // waiting on `z`, but it won't complete since we're also background
45+
await loopUntil(priority: .userInitiated)
46+
}
47+
48+
// detach, don't wait
49+
Task.runDetached(priority: .userInitiated) {
50+
await x // escalates x, which waits on z, so z also escalates
51+
}
52+
53+
// since `_` awaited from userInitiated on `x` we:
54+
// - boost `x` to `userInitiated`
55+
// and then since `x` waits on `z`
56+
// - `z` also gets boosted to `userInitiated`
57+
// which "unlocks" it, allowing the 'default' `await z` to complete:
58+
await x
59+
await z
60+
print("default done") // CHECK: default done
61+
}
62+
563
@main struct Main {
664
static func main() async {
7-
// FIXME: use Task.currentPriority once unsafeCurrent works
8-
let p = await Task.unsafeCurrentASYNC().task.priority
9-
assert(p == Task.Priority.default)
65+
await test_detach()
66+
await test_multiple_lo_indirectly_escalated()
1067
}
1168
}

0 commit comments

Comments
 (0)