Skip to content

Commit 66d4af0

Browse files
committed
Task priority escalation on Apple platforms
A task can be in one of 4 states over its lifetime: (a) suspended (b) enqueued (c) running (d) completed This change provides priority inversion avoidance support if a task gets escalated when it is in state (a), (c), (d). Radar-Id: rdar://problem/76127624
1 parent 519e60c commit 66d4af0

File tree

11 files changed

+534
-57
lines changed

11 files changed

+534
-57
lines changed

include/swift/ABI/Task.h

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,22 @@ class AsyncTask : public Job {
290290
return ResumeTask(ResumeContext); // 'return' forces tail call
291291
}
292292

293+
/// A task can have the following states:
294+
/// * suspended: In this state, a task is considered not runnable
295+
/// * enqueued: In this state, a task is considered runnable
296+
/// * running on a thread
297+
/// * completed
298+
///
299+
/// The following state transitions are possible:
300+
/// suspended -> enqueued
301+
/// suspended -> running
302+
/// enqueued -> running
303+
/// running -> suspended
304+
/// running -> completed
305+
/// running -> enqueued
306+
///
307+
/// The 4 methods below are how a task switches from one state to another.
308+
293309
/// Flag that this task is now running. This can update
294310
/// the priority stored in the job flags if the priority has been
295311
/// escalated.
@@ -298,14 +314,12 @@ class AsyncTask : public Job {
298314
/// ActiveTask.
299315
void flagAsRunning();
300316

301-
/// Flag that this task is now suspended. This can update the
302-
/// priority stored in the job flags if the priority hsa been
303-
/// escalated. Generally this should be done immediately after
304-
/// clearing ActiveTask and immediately before enqueuing the task
305-
/// somewhere. TODO: record where the task is enqueued if
306-
/// possible.
317+
/// Flag that this task is now suspended.
307318
void flagAsSuspended();
308319

320+
/// Flag that the task is to be enqueued on the provided executor
321+
void flagAsEnqueuedOnExecutor(ExecutorRef newExecutor);
322+
309323
/// Flag that this task is now completed. This normally does not do anything
310324
/// but can be used to locally insert logging.
311325
void flagAsCompleted();

include/swift/Runtime/Concurrency.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,17 @@ void swift_task_switch(SWIFT_ASYNC_CONTEXT AsyncContext *resumeToContext,
627627
TaskContinuationFunction *resumeFunction,
628628
ExecutorRef newExecutor);
629629

630+
/// Mark a task for enqueue on a new executor and then enqueue it.
631+
///
632+
/// The resumption function pointer and continuation should be set
633+
/// appropriately in the task.
634+
///
635+
/// Generally you should call swift_task_switch to switch execution
636+
/// synchronously when possible.
637+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
638+
void
639+
swift_task_enqueueTaskOnExecutor(AsyncTask *task, ExecutorRef executor);
640+
630641
/// Enqueue the given job to run asynchronously on the given executor.
631642
///
632643
/// The resumption function pointer and continuation should be set

include/swift/Runtime/DispatchShims.h

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//===--- DispatchShims.h - Shims for dispatch vended APIs --------------------*- C++ -*-//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#ifndef SWIFT_CONCURRENCY_DISPATCHSHIMS_H
14+
#define SWIFT_CONCURRENCY_DISPATCHSHIMS_H
15+
16+
#include "Concurrency.h"
17+
18+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
19+
#include <dispatch/swift_concurrency_private.h>
20+
21+
// Provide wrappers with runtime checks to make sure that the dispatch functions
22+
// are only called on OS-es where they are supported
23+
dispatch_thread_override_info_s
24+
swift_dispatch_thread_get_current_override_qos_floor()
25+
{
26+
if (__builtin_available(macOS 9998, iOS 9998, tvOS 9998, watchOS 9998, *)) {
27+
return dispatch_thread_get_current_override_qos_floor();
28+
}
29+
30+
return (dispatch_thread_override_info_s) {0};
31+
}
32+
33+
int
34+
swift_dispatch_thread_override_self(qos_class_t override_qos) {
35+
36+
if (__builtin_available(macOS 9998, iOS 9998, tvOS 9998, watchOS 9998, *)) {
37+
return dispatch_thread_override_self(override_qos);
38+
}
39+
40+
return 0;
41+
}
42+
43+
int
44+
swift_dispatch_lock_override_start_with_debounce(dispatch_lock_t *lock_addr,
45+
dispatch_tid_t expected_thread, qos_class_t override_to_apply) {
46+
47+
if (__builtin_available(macOS 9998, iOS 9998, tvOS 9998, watchOS 9998, *)) {
48+
return dispatch_lock_override_start_with_debounce(lock_addr, expected_thread, override_to_apply);
49+
}
50+
51+
return 0;
52+
}
53+
54+
int
55+
swift_dispatch_lock_override_end(qos_class_t override_to_end) {
56+
if (__builtin_available(macOS 9998, iOS 9998, tvOS 9998, watchOS 9998, *)) {
57+
return dispatch_lock_override_end(override_to_end);
58+
}
59+
60+
return 0;
61+
}
62+
#endif
63+
64+
#endif /* SWIFT_CONCURRENCY_DISPATCHSHIMS_H */

stdlib/public/CompatibilityOverride/CompatibilityOverrideConcurrency.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,10 @@ OVERRIDE_TASK(task_suspend, AsyncTask *,
159159
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift),
160160
swift::, ,)
161161

162+
OVERRIDE_TASK(task_enqueueTaskOnExecutor, void,
163+
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift), swift::,
164+
(AsyncTask *task, ExecutorRef newExecutor), (task, newExecutor))
165+
162166
OVERRIDE_TASK(continuation_init, AsyncTask *,
163167
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift),
164168
swift::, (ContinuationAsyncContext *context,

stdlib/public/Concurrency/Actor.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1617,9 +1617,9 @@ static void swift_task_switchImpl(SWIFT_ASYNC_CONTEXT AsyncContext *resumeContex
16171617
// executor.
16181618
SWIFT_TASK_DEBUG_LOG("switch failed, task %p enqueued on executor %p", task,
16191619
newExecutor.getIdentity());
1620-
task->flagAsSuspended();
1620+
1621+
task->flagAsEnqueuedOnExecutor(newExecutor);
16211622
_swift_task_clearCurrent();
1622-
swift_task_enqueue(task, newExecutor);
16231623
}
16241624

16251625
/*****************************************************************************/

stdlib/public/Concurrency/Task.cpp

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,12 @@ FutureFragment::Status AsyncTask::waitFuture(AsyncTask *waitingTask,
145145
queueHead, newQueueHead,
146146
/*success*/ std::memory_order_release,
147147
/*failure*/ std::memory_order_acquire)) {
148+
148149
// Escalate the priority of this task based on the priority
149150
// of the waiting task.
150-
swift_task_escalate(this, waitingTask->Flags.getPriority());
151+
auto status = waitingTask->_private().Status.load(std::memory_order_relaxed);
152+
swift_task_escalate(this, status.getStoredPriority());
153+
151154
_swift_task_clearCurrent();
152155
return FutureFragment::Status::Executing;
153156
}
@@ -231,7 +234,7 @@ void AsyncTask::completeFuture(AsyncContext *context) {
231234

232235
// Enqueue the waiter on the global executor.
233236
// TODO: allow waiters to fill in a suggested executor
234-
swift_task_enqueueGlobal(waitingTask);
237+
waitingTask->flagAsEnqueuedOnExecutor(ExecutorRef::generic());
235238

236239
// Move to the next task.
237240
waitingTask = nextWaitingTask;
@@ -649,12 +652,7 @@ static AsyncTaskAndContext swift_task_create_commonImpl(
649652
basePriority = JobPriority::Default;
650653
}
651654

652-
SWIFT_TASK_DEBUG_LOG("Task's base priority = %d", basePriority);
653-
654-
// TODO (rokhinip): Figure out the semantics of the job priority and where
655-
// it ought to be set conclusively - seems like it ought to be at enqueue
656-
// time. For now, maintain current semantics of setting jobPriority as well.
657-
jobFlags.setPriority(basePriority);
655+
SWIFT_TASK_DEBUG_LOG("Task's base priority = %#x", basePriority);
658656

659657
// Figure out the size of the header.
660658
size_t headerSize = sizeof(AsyncTask);
@@ -849,7 +847,7 @@ static AsyncTaskAndContext swift_task_create_commonImpl(
849847
// If we're supposed to enqueue the task, do so now.
850848
if (taskCreateFlags.enqueueJob()) {
851849
swift_retain(task);
852-
swift_task_enqueue(task, executor);
850+
task->flagAsEnqueuedOnExecutor(executor);
853851
}
854852

855853
return {task, initialContext};
@@ -1018,6 +1016,13 @@ static AsyncTask *swift_task_suspendImpl() {
10181016
return task;
10191017
}
10201018

1019+
SWIFT_CC(swift)
1020+
static void
1021+
swift_task_enqueueTaskOnExecutorImpl(AsyncTask *task, ExecutorRef executor)
1022+
{
1023+
task->flagAsEnqueuedOnExecutor(executor);
1024+
}
1025+
10211026
SWIFT_CC(swift)
10221027
static AsyncTask *swift_continuation_initImpl(ContinuationAsyncContext *context,
10231028
AsyncContinuationFlags flags) {
@@ -1146,7 +1151,7 @@ static void resumeTaskAfterContinuation(AsyncTask *task,
11461151
// to make a stronger best-effort attempt to catch racing attempts to
11471152
// resume the continuation?
11481153

1149-
swift_task_enqueue(task, context->ResumeToExecutor);
1154+
task->flagAsEnqueuedOnExecutor(context->ResumeToExecutor);
11501155
}
11511156

11521157
SWIFT_CC(swift)

stdlib/public/Concurrency/TaskGroup.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -622,7 +622,7 @@ void TaskGroupImpl::offer(AsyncTask *completedTask, AsyncContext *context) {
622622
_swift_tsan_acquire(static_cast<Job *>(waitingTask));
623623

624624
// TODO: allow the caller to suggest an executor
625-
swift_task_enqueueGlobal(waitingTask);
625+
waitingTask->flagAsEnqueuedOnExecutor(ExecutorRef::generic());
626626
return;
627627
} // else, try again
628628
}

0 commit comments

Comments
 (0)