Files
swift-mirror/test/Concurrency/async_task_escalate_priority.swift
Konrad `ktoso` Malawski 503d80699e Don't disable priority escalation tests on arm64e
We carried over all unsupported: marks from task_priority test which seems to have had just racy behavior for ages. This test should be fine on arm64e, and especially, we must run it to verify the pointer auth of these APIs.

This allowed a pointer auth issue to slip through, so let's remedy this by actually running the test on those platforms.
2025-06-02 21:45:04 +09:00

240 lines
8.9 KiB
Swift

// RUN: %empty-directory(%t)
// RUN: %target-build-swift %s -Xfrontend -disable-availability-checking -parse-as-library -o %t/async_task_escalate_priority
// RUN: %target-codesign %t/async_task_escalate_priority
// RUN: %target-run %t/async_task_escalate_priority
// REQUIRES: VENDOR=apple
// REQUIRES: executable_test
// REQUIRES: concurrency
// REQUIRES: libdispatch
// rdar://76038845
// REQUIRES: concurrency_runtime
// UNSUPPORTED: back_deployment_runtime
// UNSUPPORTED: back_deploy_concurrency
// rdar://101077408 - Temporarily disable on watchOS & iOS simulator
// UNSUPPORTED: DARWIN_SIMULATOR=watchos
// UNSUPPORTED: DARWIN_SIMULATOR=ios
// UNSUPPORTED: DARWIN_SIMULATOR=tvos
import Darwin
@preconcurrency import Dispatch
func p(_ message: String, file: String = #fileID, line: UInt = #line) {
print("[:\(line)] \(message)")
}
func expectEqual(_ l: TaskPriority, _ r: TaskPriority,
function: String = #function, file: String = #fileID, line: UInt = #line) {
precondition(l == r, "Priority [\(l)] did not equal \(r) @ \(file):\(line)(\(function))")
}
func loopUntil(priority: TaskPriority, file: String = #fileID, line: UInt = #line) async {
var loops = 10
var currentPriority = Task.currentPriority
while (currentPriority != priority) {
p("[\(file):\(line)] Current priority = \(currentPriority) != \(priority)")
await Task.sleep(1_000_000)
currentPriority = Task.currentPriority
loops -= 1
if loops < 1 {
fatalError("Task priority was never: \(priority), over multiple loops")
}
}
}
func print(_ s: String = "") {
fputs("\(s)\n", stderr)
}
func expectedBasePri(priority: TaskPriority) -> TaskPriority {
let basePri = Task.basePriority!
p("Testing basePri matching expected pri - \(basePri) == \(priority)")
expectEqual(basePri, priority)
withUnsafeCurrentTask { unsafeTask in
guard let unsafeTask else {
fatalError("Expected to be able to get current task, but could not!")
}
// The UnsafeCurrentTask must return the same value
expectEqual(basePri, unsafeTask.basePriority)
}
return basePri
}
func expectedEscalatedPri(priority: TaskPriority) -> TaskPriority {
let curPri = Task.currentPriority
p("Testing escalated matching expected pri - \(curPri) == \(priority)")
expectEqual(curPri, priority)
return curPri
}
func testNestedTaskPriority(basePri: TaskPriority, curPri: TaskPriority) async {
let _ = expectedBasePri(priority: basePri)
let _ = expectedEscalatedPri(priority: curPri)
}
@main struct Main {
static func main() async {
let top_level = Task.detached { /* To detach from main actor when running work */
func basicTask_escalateWhenTaskIsRunning() {
let sem1 = DispatchSemaphore(value: 0)
let sem2 = DispatchSemaphore(value: 0)
let task = Task(priority: .background) {
let _ = expectedBasePri(priority: .background)
// Wait until task is running before asking to be escalated
sem1.signal()
sleep(1)
await loopUntil(priority: .default)
sem2.signal()
}
// Wait till child runs and asks to be escalated
sem1.wait()
task.escalatePriority(to: .default)
sem2.wait()
}
basicTask_escalateWhenTaskIsRunning()
func triggerTaskEscalationHandler() {
let sem1 = DispatchSemaphore(value: 0)
let sem2 = DispatchSemaphore(value: 0)
let semEscalated = DispatchSemaphore(value: 0)
let task = Task(priority: .background) {
let _ = expectedBasePri(priority: .background)
await withTaskPriorityEscalationHandler {
p("in withTaskPriorityEscalationHandler, Task.currentPriority = \(Task.currentPriority)")
// Wait until task is running before asking to be escalated
sem1.signal()
sleep(1)
await loopUntil(priority: .default)
p("in withTaskPriorityEscalationHandler, after loop, Task.currentPriority = \(Task.currentPriority)")
} onPriorityEscalated: { oldPriority, newPriority in
p("in onPriorityEscalated Task.currentPriority = \(Task.currentPriority)")
p("in onPriorityEscalated oldPriority = \(oldPriority)")
precondition(oldPriority == .background, "old Priority was: \(oldPriority)")
p("in onPriorityEscalated newPriority = \(newPriority)")
precondition(newPriority == .default, "new Priority was: \(newPriority)")
semEscalated.signal()
}
p("Current priority = \(Task.currentPriority)")
p("after withTaskPriorityEscalationHandler")
sem2.signal()
}
// Wait till child runs and asks to be escalated
sem1.wait()
task.cancel() // just checking the records don't stomp onto each other somehow
task.escalatePriority(to: .default)
semEscalated.wait()
sem2.wait()
}
triggerTaskEscalationHandler()
func triggerTwice_escalateToMediumAndThenAgainToHigh() {
let sem1 = DispatchSemaphore(value: 0)
let semEscalatedMedium = DispatchSemaphore(value: 0)
let semEscalatedHigh = DispatchSemaphore(value: 0)
let semEscalatedInHandler = DispatchSemaphore(value: 2)
let task = Task(priority: .background) {
let _ = expectedBasePri(priority: .background)
await withTaskPriorityEscalationHandler {
p("in withTaskPriorityEscalationHandler, Task.currentPriority = \(Task.currentPriority)")
sem1.signal()
p("in withTaskPriorityEscalationHandler, wait for escalation -> \(TaskPriority.medium)")
await loopUntil(priority: .medium)
p("in withTaskPriorityEscalationHandler, after loop, Task.currentPriority = \(Task.currentPriority)")
semEscalatedMedium.signal()
p("in withTaskPriorityEscalationHandler, wait for escalation -> \(TaskPriority.high)")
await loopUntil(priority: .high)
p("in withTaskPriorityEscalationHandler, after loop, Task.currentPriority = \(Task.currentPriority)")
semEscalatedHigh.signal()
} onPriorityEscalated: { oldPriority, newPriority in
p("in onPriorityEscalated Task.currentPriority = \(Task.currentPriority)") // caller priority
p("in onPriorityEscalated oldPriority = \(oldPriority)")
p("in onPriorityEscalated newPriority = \(newPriority)")
semEscalatedInHandler.signal()
}
p("after withTaskPriorityEscalationHandler")
}
// Wait till child runs and asks to be escalated
sem1.wait()
task.escalatePriority(to: .medium)
semEscalatedMedium.wait()
task.escalatePriority(to: .high)
semEscalatedHigh.wait()
// we got escalated twice
semEscalatedInHandler.wait()
}
triggerTwice_escalateToMediumAndThenAgainToHigh()
func dontTriggerInAlreadyEscalatedTask() {
let sem1 = DispatchSemaphore(value: 0)
let sem2 = DispatchSemaphore(value: 0)
let semEscalated = DispatchSemaphore(value: 0)
let task = Task(priority: .background) {
let _ = expectedBasePri(priority: .background)
await withTaskPriorityEscalationHandler {
p("in withTaskPriorityEscalationHandler, Task.currentPriority = \(Task.currentPriority)")
sem1.signal()
await loopUntil(priority: .default)
p("in withTaskPriorityEscalationHandler, after loop, Task.currentPriority = \(Task.currentPriority)")
} onPriorityEscalated: { oldPriority, newPriority in
p("in onPriorityEscalated Task.currentPriority = \(Task.currentPriority)")
p("in onPriorityEscalated oldPriority = \(oldPriority)")
precondition(oldPriority == .background, "old Priority was: \(oldPriority)")
p("in onPriorityEscalated newPriority = \(newPriority)")
precondition(newPriority == .default, "new Priority was: \(newPriority)")
semEscalated.signal()
}
// already escalated
await loopUntil(priority: .default)
await withTaskPriorityEscalationHandler {
await loopUntil(priority: .default)
p("in withTaskPriorityEscalationHandler, after loop, Task.currentPriority = \(Task.currentPriority)")
} onPriorityEscalated: { oldPriority, newPriority in
fatalError("Task was already escalated earlier, no escalation triggered here")
}
p("Current priority = \(Task.currentPriority)")
p("after withTaskPriorityEscalationHandler")
sem2.signal()
}
// Wait till child runs and asks to be escalated
sem1.wait()
task.cancel() // just checking the records don't stomp onto each other somehow
task.escalatePriority(to: .default)
semEscalated.wait()
sem2.wait()
}
dontTriggerInAlreadyEscalatedTask()
}
await top_level.value
}
}