Skip to content

Commit 18c2584

Browse files
committed
[Concurrency] Task.escalatePriority
1 parent c4af3b3 commit 18c2584

File tree

2 files changed

+178
-0
lines changed

2 files changed

+178
-0
lines changed

stdlib/public/Concurrency/Task.swift

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -900,6 +900,70 @@ extension Task where Success == Never, Failure == Never {
900900
}
901901
}
902902

903+
// ==== Manual Priority Escalation ---------------------------------------------
904+
905+
extension Task {
906+
/// Escalate the task `priority` of the passed in task to the `newPriority`.
907+
///
908+
/// - Warning: This API should rarely be used, and instead you can rely on
909+
/// structured concurrency and implicit priority escalation which happens
910+
/// when a higher priority task awaits on a result of a lower priority task.
911+
///
912+
/// I.e. using `await` on the target task usually is the correct way to
913+
/// escalate the target task to the current priority of the calling task,
914+
/// especially because in such setup if the waiting task gets escalated,
915+
/// the waited on task would be escalated automatically as well.
916+
///
917+
/// The concurrency runtime is free to interpret and handle escalation
918+
/// depending on platform characteristics.
919+
///
920+
/// Priority escalation is propagated to child tasks of the waited-on task,
921+
/// and will trigger any priority escalation handlers, if any were registered.
922+
///
923+
/// Escalation can only *increase* the priority of a task, and
924+
/// de-escalating priority is not supported.
925+
///
926+
/// This method can be called from any task or thread.
927+
///
928+
/// - Parameters:
929+
/// - task: the task which to escalate the priority of
930+
/// - newPriority: the new priority the task should continue executing on
931+
@available(SwiftStdlib 9999, *)
932+
public static func escalatePriority(_ task: UnsafeCurrentTask, to newPriority: TaskPriority) {
933+
_taskEscalate(task._task, newPriority: newPriority.rawValue)
934+
}
935+
936+
/// Escalate the task `priority` of the passed in task to the `newPriority`.
937+
///
938+
/// - Warning: This API should rarely be used, and instead you can rely on
939+
/// structured concurrency and implicit priority escalation which happens
940+
/// when a higher priority task awaits on a result of a lower priority task.
941+
///
942+
/// I.e. using `await` on the target task usually is the correct way to
943+
/// escalate the target task to the current priority of the calling task,
944+
/// especially because in such setup if the waiting task gets escalated,
945+
/// the waited on task would be escalated automatically as well.
946+
///
947+
/// The concurrency runtime is free to interpret and handle escalation
948+
/// depending on platform characteristics.
949+
///
950+
/// Priority escalation is propagated to child tasks of the waited-on task,
951+
/// and will trigger any priority escalation handlers, if any were registered.
952+
///
953+
/// Escalation can only *increase* the priority of a task, and
954+
/// de-escalating priority is not supported.
955+
///
956+
/// This method can be called from any task or thread.
957+
///
958+
/// - Parameters:
959+
/// - task: the task which to escalate the priority of
960+
/// - newPriority: the new priority the task should continue executing on
961+
@available(SwiftStdlib 9999, *)
962+
public static func escalatePriority(_ task: UnsafeCurrentTask, to newPriority: TaskPriority) {
963+
_taskEscalate(task._task, newPriority: newPriority.rawValue)
964+
}
965+
}
966+
903967
// ==== UnsafeCurrentTask ------------------------------------------------------
904968

905969
/// Calls a closure with an unsafe reference to the current task.
@@ -1152,6 +1216,11 @@ func _taskIsCancelled(_ task: Builtin.NativeObject) -> Bool
11521216
@_silgen_name("swift_task_currentPriority")
11531217
internal func _taskCurrentPriority(_ task: Builtin.NativeObject) -> UInt8
11541218

1219+
@available(SwiftStdlib 9999, *) // TODO: determine how far back this can back-deploy because it already was in runtime
1220+
@_silgen_name("swift_task_escalate")
1221+
internal func _taskEscalate(_ task: Builtin.NativeObject, newPriority: UInt8)
1222+
1223+
@available(SwiftStdlib 5.1, *)
11551224
@_silgen_name("swift_task_basePriority")
11561225
internal func _taskBasePriority(_ task: Builtin.NativeObject) -> UInt8
11571226

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// RUN: %empty-directory(%t)
2+
3+
// RUN: %target-build-swift %s -Xfrontend -disable-availability-checking -parse-as-library -o %t/async_task_priority
4+
// RUN: %target-codesign %t/async_task_priority
5+
// RUN: %target-run %t/async_task_priority
6+
7+
// REQUIRES: VENDOR=apple
8+
// REQUIRES: executable_test
9+
// REQUIRES: concurrency
10+
// REQUIRES: libdispatch
11+
12+
// rdar://76038845
13+
// REQUIRES: concurrency_runtime
14+
// UNSUPPORTED: back_deployment_runtime
15+
// UNSUPPORTED: back_deploy_concurrency
16+
17+
// rdar://101077408 - Temporarily disable on watchOS & iOS simulator
18+
// UNSUPPORTED: DARWIN_SIMULATOR=watchos
19+
// UNSUPPORTED: DARWIN_SIMULATOR=ios
20+
// UNSUPPORTED: DARWIN_SIMULATOR=tvos
21+
22+
// rdar://107390341 - Temporarily disable for arm64e
23+
// UNSUPPORTED: CPU=arm64e
24+
25+
import Darwin
26+
@preconcurrency import Dispatch
27+
import StdlibUnittest
28+
29+
func loopUntil(priority: TaskPriority) async {
30+
var loops = 100
31+
var currentPriority = Task.currentPriority
32+
while (currentPriority != priority) {
33+
print("Current priority = \(currentPriority) != \(priority)")
34+
await Task.sleep(1_000_000)
35+
currentPriority = Task.currentPriority
36+
loops -= 1
37+
if loops < 1 {
38+
fatalError("Task priority was never: \(priority), over multiple loops")
39+
}
40+
}
41+
}
42+
43+
func print(_ s: String = "") {
44+
fputs("\(s)\n", stderr)
45+
}
46+
47+
func expectedBasePri(priority: TaskPriority) -> TaskPriority {
48+
let basePri = Task.basePriority!
49+
print("Testing basePri matching expected pri - \(basePri) == \(priority)")
50+
expectEqual(basePri, priority)
51+
withUnsafeCurrentTask { unsafeTask in
52+
guard let unsafeTask else {
53+
fatalError("Expected to be able to get current task, but could not!")
54+
}
55+
// The UnsafeCurrentTask must return the same value
56+
expectEqual(basePri, unsafeTask.basePriority)
57+
}
58+
59+
return basePri
60+
}
61+
62+
func expectedEscalatedPri(priority: TaskPriority) -> TaskPriority {
63+
let curPri = Task.currentPriority
64+
print("Testing escalated matching expected pri - \(curPri) == \(priority)")
65+
expectEqual(curPri, priority)
66+
67+
return curPri
68+
}
69+
70+
func testNestedTaskPriority(basePri: TaskPriority, curPri: TaskPriority) async {
71+
let _ = expectedBasePri(priority: basePri)
72+
let _ = expectedEscalatedPri(priority: curPri)
73+
}
74+
75+
@main struct Main {
76+
static func main() async {
77+
78+
let top_level = Task.detached { /* To detach from main actor when running work */
79+
80+
let tests = TestSuite("Task Priority escalation")
81+
if #available(SwiftStdlib 5.1, *) {
82+
83+
tests.test("Basic task_escalate when task is running") {
84+
let sem1 = DispatchSemaphore(value: 0)
85+
let sem2 = DispatchSemaphore(value: 0)
86+
let task = Task(priority: .background) {
87+
let _ = expectedBasePri(priority: .background)
88+
89+
// Wait until task is running before asking to be escalated
90+
sem1.signal()
91+
sleep(1)
92+
93+
await loopUntil(priority: .default)
94+
sem2.signal()
95+
}
96+
97+
// Wait till child runs and asks to be escalated
98+
sem1.wait()
99+
Task.escalatePriority(task, to: .default)
100+
sem2.wait()
101+
}
102+
}
103+
104+
await runAllTestsAsync()
105+
}
106+
107+
await top_level.value
108+
}
109+
}

0 commit comments

Comments
 (0)