Files
swift-mirror/test/Concurrency/async_task_escalate_priority.swift
2025-02-10 17:55:42 +09:00

229 lines
8.2 KiB
Swift

// RUN: %empty-directory(%t)
// RUN: %target-build-swift %s -Xfrontend -disable-availability-checking -parse-as-library -o %t/async_task_priority
// RUN: %target-codesign %t/async_task_priority
// RUN: %target-run %t/async_task_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
// rdar://107390341 - Because task escalation tests seem disabled on this platform
// UNSUPPORTED: CPU=arm64e
import Darwin
@preconcurrency import Dispatch
import StdlibUnittest
func loopUntil(priority: TaskPriority) async {
var loops = 10
var currentPriority = Task.currentPriority
while (currentPriority != priority) {
print("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!
print("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
print("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 */
let tests = TestSuite("Task Priority escalation")
tests.test("Basic task_escalate when task is running") {
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(task, to: .default)
sem2.wait()
}
tests.test("Trigger task escalation handler") {
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 {
print("in withTaskPriorityEscalationHandler, Task.currentPriority = \(Task.currentPriority)")
// Wait until task is running before asking to be escalated
sem1.signal()
sleep(1)
await loopUntil(priority: .default)
print("in withTaskPriorityEscalationHandler, after loop, Task.currentPriority = \(Task.currentPriority)")
} onPriorityEscalated: { newPriority in
print("in onPriorityEscalated Task.currentPriority = \(Task.currentPriority)")
print("in onPriorityEscalated newPriority = \(newPriority)")
precondition(newPriority == .default)
semEscalated.signal()
}
print("Current priority = \(Task.currentPriority)")
print("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(task, to: .default)
semEscalated.wait()
sem2.wait()
}
tests.test("Trigger twice: Escalate to medium, and then again to high") {
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 {
print("in withTaskPriorityEscalationHandler, Task.currentPriority = \(Task.currentPriority)")
sem1.signal()
print("in withTaskPriorityEscalationHandler, wait for escalation -> \(TaskPriority.medium)")
await loopUntil(priority: .medium)
print("in withTaskPriorityEscalationHandler, after loop, Task.currentPriority = \(Task.currentPriority)")
semEscalatedMedium.signal()
print("in withTaskPriorityEscalationHandler, wait for escalation -> \(TaskPriority.high)")
await loopUntil(priority: .high)
print("in withTaskPriorityEscalationHandler, after loop, Task.currentPriority = \(Task.currentPriority)")
semEscalatedHigh.signal()
} onPriorityEscalated: { newPriority in
print("in onPriorityEscalated Task.currentPriority = \(Task.currentPriority)") // caller priority
print("in onPriorityEscalated newPriority = \(newPriority)")
semEscalatedInHandler.signal()
}
print("after withTaskPriorityEscalationHandler")
}
// Wait till child runs and asks to be escalated
sem1.wait()
Task.escalatePriority(task, to: .medium)
semEscalatedMedium.wait()
Task.escalatePriority(task, to: .high)
semEscalatedHigh.wait()
// we got escalated twice
semEscalatedInHandler.wait()
}
tests.test("Don't trigger in already escalated task") {
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 {
print("in withTaskPriorityEscalationHandler, Task.currentPriority = \(Task.currentPriority)")
sem1.signal()
await loopUntil(priority: .default)
print("in withTaskPriorityEscalationHandler, after loop, Task.currentPriority = \(Task.currentPriority)")
} onPriorityEscalated: { newPriority in
print("in onPriorityEscalated Task.currentPriority = \(Task.currentPriority)")
print("in onPriorityEscalated newPriority = \(newPriority)")
precondition(newPriority == .default)
semEscalated.signal()
}
// already escalated
await loopUntil(priority: .default)
await withTaskPriorityEscalationHandler {
await loopUntil(priority: .default)
print("in withTaskPriorityEscalationHandler, after loop, Task.currentPriority = \(Task.currentPriority)")
} onPriorityEscalated: { newPriority in
fatalError("Task was already escalated earlier, no escalation triggered here")
}
print("Current priority = \(Task.currentPriority)")
print("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(task, to: .default)
semEscalated.wait()
sem2.wait()
}
await runAllTestsAsync()
}
await top_level.value
}
}