Skip to content

Commit f5e5a07

Browse files
authored
Merge pull request swiftlang#40718 from apple/rokhinip/86100376-change-task-propagation-rules
Change task priority propagation rules
2 parents a7823cc + a2a5c64 commit f5e5a07

File tree

10 files changed

+323
-67
lines changed

10 files changed

+323
-67
lines changed

include/swift/ABI/MetadataValues.h

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2014,12 +2014,12 @@ enum class JobKind : size_t {
20142014
enum class JobPriority : size_t {
20152015
// This is modelled off of Dispatch.QoS, and the values are directly
20162016
// stolen from there.
2017-
UserInteractive = 0x21,
2018-
UserInitiated = 0x19,
2019-
Default = 0x15,
2020-
Utility = 0x11,
2021-
Background = 0x09,
2022-
Unspecified = 0x00,
2017+
UserInteractive = 0x21, /* UI */
2018+
UserInitiated = 0x19, /* IN */
2019+
Default = 0x15, /* DEF */
2020+
Utility = 0x11, /* UT */
2021+
Background = 0x09, /* BG */
2022+
Unspecified = 0x00, /* UN */
20232023
};
20242024

20252025
/// A tri-valued comparator which orders higher priorities first.
@@ -2028,12 +2028,18 @@ inline int descendingPriorityOrder(JobPriority lhs,
20282028
return (lhs == rhs ? 0 : lhs > rhs ? -1 : 1);
20292029
}
20302030

2031+
inline JobPriority withUserInteractivePriorityDowngrade(JobPriority priority) {
2032+
return (priority == JobPriority::UserInteractive) ? JobPriority::UserInitiated
2033+
: priority;
2034+
}
2035+
20312036
/// Flags for task creation.
20322037
class TaskCreateFlags : public FlagSet<size_t> {
20332038
public:
20342039
enum {
2035-
Priority = 0,
2036-
Priority_width = 8,
2040+
// Priority that user specified while creating the task
2041+
RequestedPriority = 0,
2042+
RequestedPriority_width = 8,
20372043

20382044
Task_IsChildTask = 8,
20392045
// bit 9 is unused
@@ -2046,8 +2052,9 @@ class TaskCreateFlags : public FlagSet<size_t> {
20462052
explicit constexpr TaskCreateFlags(size_t bits) : FlagSet(bits) {}
20472053
constexpr TaskCreateFlags() {}
20482054

2049-
FLAGSET_DEFINE_FIELD_ACCESSORS(Priority, Priority_width, JobPriority,
2050-
getPriority, setPriority)
2055+
FLAGSET_DEFINE_FIELD_ACCESSORS(RequestedPriority, RequestedPriority_width,
2056+
JobPriority, getRequestedPriority,
2057+
setRequestedPriority)
20512058
FLAGSET_DEFINE_FLAG_ACCESSORS(Task_IsChildTask,
20522059
isChildTask,
20532060
setIsChildTask)

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: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,19 @@ size_t swift_task_getJobFlags(AsyncTask* task);
481481
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
482482
bool swift_task_isCancelled(AsyncTask* task);
483483

484+
/// Returns the current priority of the task which is >= base priority of the
485+
/// task. This function does not exist in the base ABI of this library and must
486+
/// be deployment limited
487+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
488+
JobPriority
489+
swift_task_currentPriority(AsyncTask *task);
490+
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+
484497
/// Create and add an cancellation record to the task.
485498
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
486499
CancellationNotificationStatusRecord*

stdlib/public/Concurrency/Task.cpp

Lines changed: 111 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ static void completeTaskWithClosure(SWIFT_ASYNC_CONTEXT AsyncContext *context,
413413
reinterpret_cast<char *>(context) - sizeof(AsyncContextPrefix));
414414

415415
swift_release((HeapObject *)asyncContextPrefix->closureContext);
416-
416+
417417
// Clean up the rest of the task.
418418
return completeTaskAndRelease(context, error);
419419
}
@@ -470,6 +470,32 @@ const void *AsyncTask::getResumeFunctionForLogging() {
470470
return reinterpret_cast<const void *>(ResumeTask);
471471
}
472472

473+
JobPriority swift::swift_task_currentPriority(AsyncTask *task)
474+
{
475+
// This is racey but this is to be used in an API is inherently racey anyways.
476+
auto oldStatus = task->_private().Status.load(std::memory_order_relaxed);
477+
return oldStatus.getStoredPriority();
478+
}
479+
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+
487+
static inline bool isUnspecified(JobPriority priority) {
488+
return priority == JobPriority::Unspecified;
489+
}
490+
491+
static inline bool taskIsUnstructured(JobFlags jobFlags) {
492+
return !jobFlags.task_isAsyncLetTask() && !jobFlags.task_isGroupChildTask();
493+
}
494+
495+
static inline bool taskIsDetached(TaskCreateFlags createFlags, JobFlags jobFlags) {
496+
return taskIsUnstructured(jobFlags) && !createFlags.copyTaskLocals();
497+
}
498+
473499
/// Implementation of task creation.
474500
SWIFT_CC(swift)
475501
static AsyncTaskAndContext swift_task_create_commonImpl(
@@ -478,10 +504,11 @@ static AsyncTaskAndContext swift_task_create_commonImpl(
478504
const Metadata *futureResultType,
479505
TaskContinuationFunction *function, void *closureContext,
480506
size_t initialContextSize) {
507+
481508
TaskCreateFlags taskCreateFlags(rawTaskCreateFlags);
509+
JobFlags jobFlags(JobKind::Task, JobPriority::Unspecified);
482510

483511
// Propagate task-creation flags to job flags as appropriate.
484-
JobFlags jobFlags(JobKind::Task, taskCreateFlags.getPriority());
485512
jobFlags.task_setIsChildTask(taskCreateFlags.isChildTask());
486513
if (futureResultType) {
487514
jobFlags.task_setIsFuture(true);
@@ -520,7 +547,7 @@ static AsyncTaskAndContext swift_task_create_commonImpl(
520547
// of in a FutureFragment.
521548
hasAsyncLetResultBuffer = true;
522549
assert(asyncLet && "Missing async let storage");
523-
550+
524551
jobFlags.task_setIsAsyncLetTask(true);
525552
jobFlags.task_setIsChildTask(true);
526553
break;
@@ -534,32 +561,85 @@ static AsyncTaskAndContext swift_task_create_commonImpl(
534561
}
535562

536563
AsyncTask *parent = nullptr;
564+
AsyncTask *currentTask = swift_task_getCurrent();
537565
if (jobFlags.task_isChildTask()) {
538-
parent = swift_task_getCurrent();
566+
parent = currentTask;
539567
assert(parent != nullptr && "creating a child task with no active task");
540568
}
541569

542-
// Inherit the priority of the currently-executing task if unspecified and
543-
// we want to inherit.
544-
if (jobFlags.getPriority() == JobPriority::Unspecified &&
545-
(jobFlags.task_isChildTask() || taskCreateFlags.inheritContext())) {
546-
AsyncTask *currentTask = parent;
547-
if (!currentTask)
548-
currentTask = swift_task_getCurrent();
549-
550-
if (currentTask)
551-
jobFlags.setPriority(currentTask->getPriority());
552-
else if (taskCreateFlags.inheritContext())
553-
jobFlags.setPriority(swift_task_getCurrentThreadPriority());
570+
// Start with user specified priority at creation time (if any)
571+
JobPriority basePriority = (taskCreateFlags.getRequestedPriority());
572+
573+
if (taskIsDetached(taskCreateFlags, jobFlags)) {
574+
SWIFT_TASK_DEBUG_LOG("Creating a detached task from %p", currentTask);
575+
// Case 1: No priority specified
576+
// Base priority = UN
577+
// Escalated priority = UN
578+
// Case 2: Priority specified
579+
// Base priority = user specified priority
580+
// Escalated priority = UN
581+
//
582+
// Task will be created with max priority = max(base priority, UN) = base
583+
// priority. We shouldn't need to do any additional manipulations here since
584+
// basePriority should already be the right value
585+
586+
} else if (taskIsUnstructured(jobFlags)) {
587+
SWIFT_TASK_DEBUG_LOG("Creating an unstructured task from %p", currentTask);
588+
589+
if (isUnspecified(basePriority)) {
590+
// Case 1: No priority specified
591+
// Base priority = Base priority of parent with a UI -> IN downgrade
592+
// Escalated priority = UN
593+
if (currentTask) {
594+
basePriority = currentTask->_private().BasePriority;
595+
} else {
596+
basePriority = swift_task_getCurrentThreadPriority();
597+
}
598+
basePriority = withUserInteractivePriorityDowngrade(basePriority);
599+
} else {
600+
// Case 2: User specified a priority
601+
// Base priority = user specified priority
602+
// Escalated priority = UN
603+
}
604+
605+
// Task will be created with max priority = max(base priority, UN) = base
606+
// priority
607+
} else {
608+
// Is a structured concurrency child task. Must have a parent.
609+
assert((asyncLet || group) && parent);
610+
SWIFT_TASK_DEBUG_LOG("Creating an structured concurrency task from %p", currentTask);
611+
612+
if (isUnspecified(basePriority)) {
613+
// Case 1: No priority specified
614+
// Base priority = Base priority of parent with a UI -> IN downgrade
615+
// Escalated priority = Escalated priority of parent with a UI -> IN
616+
// downgrade
617+
JobPriority parentBasePri = parent->_private().BasePriority;
618+
basePriority = withUserInteractivePriorityDowngrade(parentBasePri);
619+
} else {
620+
// Case 2: User priority specified
621+
// Base priority = User specified priority
622+
// Escalated priority = Escalated priority of parent with a UI -> IN
623+
// downgrade
624+
}
625+
626+
// Task will be created with escalated priority = base priority. We will
627+
// update the escalated priority with the right rules in
628+
// updateNewChildWithParentAndGroupState when we link the child into
629+
// the parent task/task group since we'll have the right
630+
// synchronization then.
631+
}
632+
633+
if (isUnspecified(basePriority)) {
634+
basePriority = JobPriority::Default;
554635
}
555636

556-
// Adjust user-interactive priorities down to user-initiated.
557-
if (jobFlags.getPriority() == JobPriority::UserInteractive)
558-
jobFlags.setPriority(JobPriority::UserInitiated);
637+
SWIFT_TASK_DEBUG_LOG("Task's base priority = %d", basePriority);
559638

560-
// If there is still no job priority, use the default priority.
561-
if (jobFlags.getPriority() == JobPriority::Unspecified)
562-
jobFlags.setPriority(JobPriority::Default);
639+
// TODO (rokhinip): Figure out the semantics of the job priority and where
640+
// it ought to be set conclusively - seems like it ought to be at enqueue
641+
// time. For now, maintain current semantics of setting jobPriority as well.
642+
jobFlags.setPriority(basePriority);
563643

564644
// Figure out the size of the header.
565645
size_t headerSize = sizeof(AsyncTask);
@@ -591,14 +671,14 @@ static AsyncTaskAndContext swift_task_create_commonImpl(
591671
void *allocation = nullptr;
592672
if (asyncLet) {
593673
assert(parent);
594-
674+
595675
// If there isn't enough room in the fixed async let allocation to
596676
// set up the initial context, then we'll have to allocate more space
597677
// from the parent.
598678
if (asyncLet->getSizeOfPreallocatedSpace() < amountToAllocate) {
599679
hasAsyncLetResultBuffer = false;
600680
}
601-
681+
602682
// DEPRECATED. This is separated from the above condition because we
603683
// also have to handle an older async let ABI that did not provide
604684
// space for the initial slab in the compiler-generated preallocation.
@@ -653,7 +733,7 @@ static AsyncTaskAndContext swift_task_create_commonImpl(
653733
// Initialize the task so that resuming it will run the given
654734
// function on the initial context.
655735
AsyncTask *task = nullptr;
656-
bool captureCurrentVoucher = taskCreateFlags.copyTaskLocals() || jobFlags.task_isChildTask();
736+
bool captureCurrentVoucher = !taskIsDetached(taskCreateFlags, jobFlags);
657737
if (asyncLet) {
658738
// Initialize the refcount bits to "immortal", so that
659739
// ARC operations don't have any effect on the task.
@@ -678,7 +758,7 @@ static AsyncTaskAndContext swift_task_create_commonImpl(
678758
auto groupChildFragment = task->groupChildFragment();
679759
new (groupChildFragment) AsyncTask::GroupChildFragment(group);
680760
}
681-
761+
682762
// Initialize the future fragment if applicable.
683763
if (futureResultType) {
684764
assert(task->isFuture());
@@ -694,7 +774,7 @@ static AsyncTaskAndContext swift_task_create_commonImpl(
694774
futureAsyncContextPrefix->indirectResult = futureFragment->getStoragePtr();
695775
}
696776

697-
SWIFT_TASK_DEBUG_LOG("creating task %p with parent %p", task, parent);
777+
SWIFT_TASK_DEBUG_LOG("creating task %p with parent %p at base pri %zu", task, parent, basePriority);
698778

699779
// Initialize the task-local allocator.
700780
initialContext->ResumeParent = reinterpret_cast<TaskContinuationFunction *>(
@@ -704,9 +784,10 @@ static AsyncTaskAndContext swift_task_create_commonImpl(
704784
if (asyncLet && initialSlabSize > 0) {
705785
assert(parent);
706786
void *initialSlab = (char*)allocation + amountToAllocate;
707-
task->Private.initializeWithSlab(task, initialSlab, initialSlabSize);
787+
task->Private.initializeWithSlab(basePriority, initialSlab,
788+
initialSlabSize);
708789
} else {
709-
task->Private.initialize(task);
790+
task->Private.initialize(basePriority);
710791
}
711792

712793
// Perform additional linking between parent and child task.
@@ -940,7 +1021,7 @@ static AsyncTask *swift_continuation_initImpl(ContinuationAsyncContext *context,
9401021
// must happen-after this call.
9411022
context->AwaitSynchronization.store(flags.isPreawaited()
9421023
? ContinuationStatus::Awaited
943-
: ContinuationStatus::Pending,
1024+
: ContinuationStatus::Pending,
9441025
std::memory_order_relaxed);
9451026

9461027
AsyncTask *task;

stdlib/public/Concurrency/Task.swift

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -289,14 +289,28 @@ extension Task where Success == Never, Failure == Never {
289289
public static var currentPriority: TaskPriority {
290290
withUnsafeCurrentTask { task in
291291
// If we are running on behalf of a task, use that task's priority.
292-
if let task = task {
293-
return task.priority
292+
if let unsafeTask = task {
293+
return TaskPriority(rawValue: _taskCurrentPriority(unsafeTask._task))
294294
}
295295

296296
// Otherwise, query the system.
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, *)
@@ -752,8 +766,7 @@ public struct UnsafeCurrentTask {
752766
/// - SeeAlso: `TaskPriority`
753767
/// - SeeAlso: `Task.currentPriority`
754768
public var priority: TaskPriority {
755-
getJobFlags(_task).priority ?? TaskPriority(
756-
rawValue: UInt8(_getCurrentThreadPriority()))
769+
TaskPriority(rawValue: _taskCurrentPriority(_task))
757770
}
758771

759772
/// Cancel the current task.
@@ -850,6 +863,12 @@ func _taskCancel(_ task: Builtin.NativeObject)
850863
@usableFromInline
851864
func _taskIsCancelled(_ task: Builtin.NativeObject) -> Bool
852865

866+
@_silgen_name("swift_task_currentPriority")
867+
internal func _taskCurrentPriority(_ task: Builtin.NativeObject) -> UInt8
868+
869+
@_silgen_name("swift_task_basePriority")
870+
internal func _taskBasePriority(_ task: Builtin.NativeObject) -> UInt8
871+
853872
@available(SwiftStdlib 5.1, *)
854873
@_silgen_name("swift_task_createNullaryContinuationJob")
855874
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 -

0 commit comments

Comments
 (0)