Skip to content

Commit 9df1c9a

Browse files
committed
Track base priority separately from max priority whereby base priority
is set at creation time. Create a new API for accessing this state Radar-Id: rdar://problem/86100376
1 parent 6d5c7b5 commit 9df1c9a

File tree

7 files changed

+189
-21
lines changed

7 files changed

+189
-21
lines changed

include/swift/ABI/Task.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -222,9 +222,9 @@ class AsyncTask : public Job {
222222
void *Storage[14];
223223

224224
/// Initialize this storage during the creation of a task.
225-
void initialize(AsyncTask *task);
226-
void initializeWithSlab(AsyncTask *task,
227-
void *slab, size_t slabCapacity);
225+
void initialize(JobPriority basePri);
226+
void initializeWithSlab(JobPriority basePri, void *slab,
227+
size_t slabCapacity);
228228

229229
/// React to the completion of the enclosing task's execution.
230230
void complete(AsyncTask *task);

include/swift/Runtime/Concurrency.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,12 @@ SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
488488
JobPriority
489489
swift_task_currentPriority(AsyncTask *task);
490490

491+
/// Returns the base priority of the task. This function does not exist in the
492+
/// base ABI of this library and must be deployment limited.
493+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
494+
JobPriority
495+
swift_task_basePriority(AsyncTask *task);
496+
491497
/// Create and add an cancellation record to the task.
492498
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
493499
CancellationNotificationStatusRecord*

stdlib/public/Concurrency/Task.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,13 @@ JobPriority swift::swift_task_currentPriority(AsyncTask *task)
477477
return oldStatus.getStoredPriority();
478478
}
479479

480+
JobPriority swift::swift_task_basePriority(AsyncTask *task)
481+
{
482+
JobPriority pri = task->_private().BasePriority;
483+
SWIFT_TASK_DEBUG_LOG("Task %p has base priority = %zu", task, pri);
484+
return pri;
485+
}
486+
480487
/// Implementation of task creation.
481488
SWIFT_CC(swift)
482489
static AsyncTaskAndContext swift_task_create_commonImpl(
@@ -568,6 +575,8 @@ static AsyncTaskAndContext swift_task_create_commonImpl(
568575
if (jobFlags.getPriority() == JobPriority::Unspecified)
569576
jobFlags.setPriority(JobPriority::Default);
570577

578+
JobPriority basePriority = jobFlags.getPriority();
579+
571580
// Figure out the size of the header.
572581
size_t headerSize = sizeof(AsyncTask);
573582
if (parent) {
@@ -711,9 +720,10 @@ static AsyncTaskAndContext swift_task_create_commonImpl(
711720
if (asyncLet && initialSlabSize > 0) {
712721
assert(parent);
713722
void *initialSlab = (char*)allocation + amountToAllocate;
714-
task->Private.initializeWithSlab(task, initialSlab, initialSlabSize);
723+
task->Private.initializeWithSlab(basePriority, initialSlab,
724+
initialSlabSize);
715725
} else {
716-
task->Private.initialize(task);
726+
task->Private.initialize(basePriority);
717727
}
718728

719729
// Perform additional linking between parent and child task.

stdlib/public/Concurrency/Task.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,20 @@ extension Task where Success == Never, Failure == Never {
297297
return TaskPriority(rawValue: UInt8(_getCurrentThreadPriority()))
298298
}
299299
}
300+
301+
/// The current task's base priority.
302+
///
303+
/// If you access this property outside of any task, this returns nil
304+
@available(SwiftStdlib 9999, *)
305+
public static var basePriority: TaskPriority? {
306+
withUnsafeCurrentTask { task in
307+
// If we are running on behalf of a task, use that task's priority.
308+
if let unsafeTask = task {
309+
return TaskPriority(rawValue: _taskBasePriority(unsafeTask._task))
310+
}
311+
return nil
312+
}
313+
}
300314
}
301315

302316
@available(SwiftStdlib 5.1, *)
@@ -852,6 +866,9 @@ func _taskIsCancelled(_ task: Builtin.NativeObject) -> Bool
852866
@_silgen_name("swift_task_currentPriority")
853867
internal func _taskCurrentPriority(_ task: Builtin.NativeObject) -> UInt8
854868

869+
@_silgen_name("swift_task_basePriority")
870+
internal func _taskBasePriority(_ task: Builtin.NativeObject) -> UInt8
871+
855872
@available(SwiftStdlib 5.1, *)
856873
@_silgen_name("swift_task_createNullaryContinuationJob")
857874
func _taskCreateNullaryContinuationJob(priority: Int, continuation: Builtin.RawUnsafeContinuation) -> Builtin.Job

stdlib/public/Concurrency/TaskGroup.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,7 @@ static void swift_taskGroup_initializeImpl(TaskGroup *group, const Metadata *T)
488488
// ==== add / attachChild ------------------------------------------------------
489489

490490
void TaskGroup::addChildTask(AsyncTask *child) {
491-
SWIFT_TASK_DEBUG_LOG("attach child task = %p to group = %p", child, group);
491+
SWIFT_TASK_DEBUG_LOG("attach child task = %p to group = %p", child, this);
492492

493493
// The counterpart of this (detachChild) is performed by the group itself,
494494
// when it offers the completed (child) task's value to a waiting task -

stdlib/public/Concurrency/TaskPrivate.h

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ class TaskFutureWaitAsyncContext : public AsyncContext {
169169
/// The current state of a task's status records.
170170
class alignas(sizeof(void*) * 2) ActiveTaskStatus {
171171
enum : uintptr_t {
172-
/// The current running priority of the task.
172+
/// The max priority of the task. This is always >= basePriority in the task
173173
PriorityMask = 0xFF,
174174

175175
/// Has the task been cancelled?
@@ -206,8 +206,8 @@ class alignas(sizeof(void*) * 2) ActiveTaskStatus {
206206
ActiveTaskStatus() = default;
207207
#endif
208208

209-
constexpr ActiveTaskStatus(JobFlags flags)
210-
: Record(nullptr), Flags(uintptr_t(flags.getPriority())) {}
209+
constexpr ActiveTaskStatus(JobPriority priority)
210+
: Record(nullptr), Flags(uintptr_t(priority)) {}
211211

212212
/// Is the task currently cancelled?
213213
bool isCancelled() const { return Flags & IsCancelled; }
@@ -317,12 +317,23 @@ struct AsyncTask::PrivateStorage {
317317
/// The top 32 bits of the task ID. The bottom 32 bits are in Job::Id.
318318
uint32_t Id;
319319

320-
PrivateStorage(JobFlags flags)
321-
: Status(ActiveTaskStatus(flags)), Local(TaskLocal::Storage()) {}
320+
/// Base priority of Task - set only at creation time of task.
321+
/// Current max priority of task is ActiveTaskStatus.
322+
///
323+
/// TODO (rokhinip): Only 8 bits of the full size_t are used. Change this into
324+
/// flagset thing so that remaining bits are available for other non-changing
325+
/// task status stuff
326+
JobPriority BasePriority;
322327

323-
PrivateStorage(JobFlags flags, void *slab, size_t slabCapacity)
324-
: Status(ActiveTaskStatus(flags)), Allocator(slab, slabCapacity),
325-
Local(TaskLocal::Storage()) {}
328+
// Always create an async task with max priority in ActiveTaskStatus = base
329+
// priority. It will be updated later if needed.
330+
PrivateStorage(JobPriority basePri)
331+
: Status(ActiveTaskStatus(basePri)), Local(TaskLocal::Storage()),
332+
BasePriority(basePri) {}
333+
334+
PrivateStorage(JobPriority basePri, void *slab, size_t slabCapacity)
335+
: Status(ActiveTaskStatus(basePri)), Allocator(slab, slabCapacity),
336+
Local(TaskLocal::Storage()), BasePriority(basePri) {}
326337

327338
void complete(AsyncTask *task) {
328339
// Destroy and deallocate any remaining task local items.
@@ -347,14 +358,12 @@ inline const AsyncTask::PrivateStorage &
347358
AsyncTask::OpaquePrivateStorage::get() const {
348359
return reinterpret_cast<const PrivateStorage &>(*this);
349360
}
350-
inline void AsyncTask::OpaquePrivateStorage::initialize(AsyncTask *task) {
351-
new (this) PrivateStorage(task->Flags);
361+
inline void AsyncTask::OpaquePrivateStorage::initialize(JobPriority basePri) {
362+
new (this) PrivateStorage(basePri);
352363
}
353-
inline void
354-
AsyncTask::OpaquePrivateStorage::initializeWithSlab(AsyncTask *task,
355-
void *slab,
356-
size_t slabCapacity) {
357-
new (this) PrivateStorage(task->Flags, slab, slabCapacity);
364+
inline void AsyncTask::OpaquePrivateStorage::initializeWithSlab(
365+
JobPriority basePri, void *slab, size_t slabCapacity) {
366+
new (this) PrivateStorage(basePri, slab, slabCapacity);
358367
}
359368
inline void AsyncTask::OpaquePrivateStorage::complete(AsyncTask *task) {
360369
get().complete(task);
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// RUN: %target-run-simple-swift( -Xfrontend -disable-availability-checking %import-libdispatch -parse-as-library)
2+
3+
// REQUIRES: executable_test
4+
// REQUIRES: concurrency
5+
// REQUIRES: libdispatch
6+
7+
// rdar://76038845
8+
// REQUIRES: concurrency_runtime
9+
// UNSUPPORTED: back_deployment_runtime
10+
11+
import StdlibUnittest
12+
import Darwin
13+
import Dispatch
14+
15+
func loopUntil(priority: TaskPriority) async {
16+
while (Task.currentPriority != priority) {
17+
await Task.sleep(1_000_000_000)
18+
}
19+
}
20+
21+
func print(_ s: String = "") {
22+
fputs("\(s)\n", stderr)
23+
}
24+
25+
func expectedBasePri(priority: TaskPriority) -> TaskPriority {
26+
let basePri = Task.basePriority!
27+
28+
print("Testing basePri matching expected pri - \(basePri) == \(priority)")
29+
expectEqual(basePri, priority)
30+
return basePri
31+
}
32+
33+
func expectedCurrentPri(priority: TaskPriority) -> TaskPriority {
34+
let curPri = Task.currentPriority
35+
print("Testing curPri matching expected pri - \(curPri) == \(priority)")
36+
expectEqual(curPri, priority)
37+
return curPri
38+
}
39+
40+
func testNestedTaskPriority(basePri: TaskPriority, curPri: TaskPriority) async {
41+
let _ = expectedBasePri(priority: basePri)
42+
let _ = expectedCurrentPri(priority: curPri)
43+
}
44+
45+
@main struct Main {
46+
static func main() async {
47+
48+
let top_level = detach { /* To detach from main actor when running work */
49+
50+
let tests = TestSuite("Task base priority")
51+
if #available(SwiftStdlib 5.1, *) {
52+
53+
tests.test("Structured concurrency base priority propagation") {
54+
let task = Task(priority: .background) {
55+
await loopUntil(priority: .default)
56+
57+
let basePri = expectedBasePri(priority: .background)
58+
let curPri = expectedCurrentPri(priority: .default)
59+
60+
// Structured concurrency via async let, escalated priority of
61+
// parent should propagate
62+
print("Testing propagation for async let structured concurrency child")
63+
async let child = testNestedTaskPriority(basePri: basePri, curPri: curPri)
64+
await child
65+
66+
let dispatchGroup = DispatchGroup()
67+
// Structured concurrency via task groups, escalated priority should
68+
// propagate
69+
await withTaskGroup(of: Void.self, returning: Void.self) { group in
70+
dispatchGroup.enter()
71+
group.addTask {
72+
print("Testing propagation for task group regular child")
73+
let _ = await testNestedTaskPriority(basePri: basePri, curPri: curPri)
74+
dispatchGroup.leave()
75+
return
76+
}
77+
78+
dispatchGroup.enter()
79+
group.addTask(priority: .utility) {
80+
print("Testing propagation for task group child with specified priority")
81+
let _ = await testNestedTaskPriority(basePri: .utility, curPri: curPri)
82+
dispatchGroup.leave()
83+
return
84+
}
85+
86+
// Wait for child tasks to finish running, don't await since that
87+
// will escalate them
88+
dispatchGroup.wait()
89+
}
90+
}
91+
92+
await task.value // Escalate task BG->DEF
93+
}
94+
95+
tests.test("Unstructured base priority propagation") {
96+
let task = Task(priority : .background) {
97+
await loopUntil(priority: .default)
98+
99+
let basePri = expectedBasePri(priority: .background)
100+
let _ = expectedCurrentPri(priority: .default)
101+
102+
let group = DispatchGroup()
103+
104+
// Create an unstructured task
105+
group.enter()
106+
let _ = Task {
107+
let _ = await testNestedTaskPriority(basePri: basePri, curPri: basePri)
108+
group.leave()
109+
return
110+
}
111+
112+
// Wait for unstructured task to finish running, don't await it
113+
// since that will escalate
114+
group.wait()
115+
}
116+
117+
await task.value // Escalate task BG->DEF
118+
}
119+
120+
}
121+
await runAllTestsAsync()
122+
}
123+
124+
await top_level.value
125+
}
126+
}

0 commit comments

Comments
 (0)