Skip to content

Commit 444f79e

Browse files
authored
Merge pull request swiftlang#63800 from apple/rokhinip/105634683-allow-addStatusRecord-to-other-task
Loosen requirement that task can only addStatusRecords to itself
2 parents 5431c6d + 3c0f4f7 commit 444f79e

File tree

7 files changed

+142
-149
lines changed

7 files changed

+142
-149
lines changed

include/swift/ABI/TaskStatus.h

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,14 @@ namespace swift {
3232
/// context), partially initialized, and then atomically added to the task with
3333
/// `addStatusRecord`.
3434
///
35-
/// Status records currently are only added to a task by itself but they may be
36-
/// removed from the task by other threads, or accessed by other threads
37-
/// asynchronous to the task for other purposes.
38-
///
39-
/// As a result, while registered with the task, a status record should only be
40-
/// modified in ways that respect the possibility of asynchronous access by a
41-
/// different thread. In particular, the chain of status records must not be
42-
/// disturbed. When the task leaves the scope that requires the status record,
43-
/// the record can be unregistered from the task with `removeStatusRecord`, at
44-
/// which point the memory can be returned to the system.
35+
/// Status records can be added to or removed from a task by itself or by other
36+
/// threads. As a result, while registered with the task, a status record
37+
/// should only be modified in ways that respect the possibility of asynchronous
38+
/// access by a different thread. In particular, the chain of status records
39+
/// must not be disturbed. When the task leaves the scope that requires the
40+
/// status record, the record can be unregistered from the task with
41+
/// `removeStatusRecord`, at which point the memory can be returned to the
42+
/// system.
4543
class TaskStatusRecord {
4644
public:
4745
TaskStatusRecordFlags Flags;

include/swift/Runtime/Atomic.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <intrin.h>
2626
#endif
2727

28+
2829
// FIXME: Workaround for rdar://problem/18889711. 'Consume' does not require
2930
// a barrier on ARM64, but LLVM doesn't know that. Although 'relaxed'
3031
// is formally UB by C++11 language rules, we should be OK because neither
@@ -36,6 +37,7 @@
3637
# define SWIFT_MEMORY_ORDER_CONSUME (std::memory_order_consume)
3738
#endif
3839

40+
3941
#if defined(_M_ARM) || defined(__arm__) || defined(__aarch64__)
4042
#define SWIFT_HAS_MSVC_ARM_ATOMICS 1
4143
#else

stdlib/public/Concurrency/AsyncLet.cpp

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,16 @@ class alignas(Alignment_AsyncLet) AsyncLetImpl: public ChildTaskStatusRecord {
4848
HasResult = 1 << 0,
4949
DidAllocateFromParentTask = 1 << 1,
5050
};
51-
51+
5252
/// The task that was kicked off to initialize this `async let`,
5353
/// and flags.
5454
llvm::PointerIntPair<AsyncTask *, 2, unsigned> taskAndFlags;
55-
55+
5656
/// Reserved space for a future_wait context frame, used during suspensions
5757
/// on the child task future.
5858
std::aligned_storage<sizeof(TaskFutureWaitAsyncContext),
5959
alignof(TaskFutureWaitAsyncContext)>::type futureWaitContextStorage;
60-
60+
6161
friend class ::swift::AsyncTask;
6262

6363
public:
@@ -77,43 +77,43 @@ class alignas(Alignment_AsyncLet) AsyncLetImpl: public ChildTaskStatusRecord {
7777
AsyncTask *getTask() const {
7878
return taskAndFlags.getPointer();
7979
}
80-
80+
8181
bool hasResultInBuffer() const {
8282
return taskAndFlags.getInt() & HasResult;
8383
}
84-
84+
8585
void setHasResultInBuffer(bool value = true) {
8686
if (value)
8787
taskAndFlags.setInt(taskAndFlags.getInt() | HasResult);
8888
else
8989
taskAndFlags.setInt(taskAndFlags.getInt() & ~HasResult);
9090
}
91-
91+
9292
bool didAllocateFromParentTask() const {
9393
return taskAndFlags.getInt() & DidAllocateFromParentTask;
9494
}
95-
95+
9696
void setDidAllocateFromParentTask(bool value = true) {
9797
if (value)
9898
taskAndFlags.setInt(taskAndFlags.getInt() | DidAllocateFromParentTask);
9999
else
100100
taskAndFlags.setInt(taskAndFlags.getInt() & ~DidAllocateFromParentTask);
101101
}
102-
102+
103103
// The compiler preallocates a large fixed space for the `async let`, with the
104104
// intent that most of it be used for the child task context. The next two
105105
// methods return the address and size of that space.
106-
106+
107107
/// Return a pointer to the unused space within the async let block.
108108
void *getPreallocatedSpace() {
109109
return (void*)(this + 1);
110110
}
111-
111+
112112
/// Return the size of the unused space within the async let block.
113113
static constexpr size_t getSizeOfPreallocatedSpace() {
114114
return sizeof(AsyncLet) - sizeof(AsyncLetImpl);
115115
}
116-
116+
117117
TaskFutureWaitAsyncContext *getFutureContext() {
118118
return reinterpret_cast<TaskFutureWaitAsyncContext*>(&futureWaitContextStorage);
119119
}
@@ -149,11 +149,11 @@ void swift::asyncLet_addImpl(AsyncTask *task, AsyncLet *asyncLet,
149149

150150
// ok, now that the async let task actually is initialized: attach it to the
151151
// current task
152-
bool addedRecord =
153-
addStatusRecord(record, [&](ActiveTaskStatus parentStatus, ActiveTaskStatus& newStatus) {
154-
updateNewChildWithParentAndGroupState(task, parentStatus, NULL);
155-
return true;
156-
});
152+
bool addedRecord = addStatusRecordToSelf(record,
153+
[&](ActiveTaskStatus parentStatus, ActiveTaskStatus& newStatus) {
154+
updateNewChildWithParentAndGroupState(task, parentStatus, NULL);
155+
return true;
156+
});
157157
(void)addedRecord;
158158
assert(addedRecord);
159159
}
@@ -279,13 +279,13 @@ static void _asyncLet_get_throwing_continuation(
279279
SWIFT_CONTEXT void *error) {
280280
auto continuationContext = static_cast<AsyncLetContinuationContext*>(callContext);
281281
auto alet = continuationContext->alet;
282-
282+
283283
// If the future completed successfully, its result is now in the async let
284284
// buffer.
285285
if (!error) {
286286
asImpl(alet)->setHasResultInBuffer();
287287
}
288-
288+
289289
// Continue the caller's execution.
290290
auto throwingResume
291291
= reinterpret_cast<ThrowingTaskFutureWaitContinuationFunction*>(callContext->ResumeParent);
@@ -303,14 +303,14 @@ static void swift_asyncLet_get_throwingImpl(
303303
if (asImpl(alet)->hasResultInBuffer()) {
304304
return resumeFunction(callerContext, nullptr);
305305
}
306-
306+
307307
auto aletContext = static_cast<AsyncLetContinuationContext*>(callContext);
308308
aletContext->ResumeParent
309309
= reinterpret_cast<TaskContinuationFunction*>(resumeFunction);
310310
aletContext->Parent = callerContext;
311311
aletContext->alet = alet;
312312
auto futureContext = asImpl(alet)->getFutureContext();
313-
313+
314314
// Unlike the non-throwing variant, whether we end up with a result depends
315315
// on the success of the task. If we raise an error, then the result buffer
316316
// will not be populated. Save the async let binding so we can fetch it
@@ -375,7 +375,7 @@ static void asyncLet_finish_after_task_completion(SWIFT_ASYNC_CONTEXT AsyncConte
375375
if (alet->didAllocateFromParentTask()) {
376376
swift_task_dealloc(task);
377377
}
378-
378+
379379
return reinterpret_cast<ThrowingTaskFutureWaitContinuationFunction*>(resumeFunction)
380380
(callerContext, error);
381381
}
@@ -390,7 +390,7 @@ static void _asyncLet_finish_continuation(
390390
= reinterpret_cast<AsyncLetContinuationContext*>(callContext);
391391
auto alet = continuationContext->alet;
392392
auto resultBuffer = continuationContext->resultBuffer;
393-
393+
394394
// Destroy the error, or the result that was stored to the buffer.
395395
if (error) {
396396
swift_errorRelease((SwiftError*)error);
@@ -436,7 +436,7 @@ static void swift_asyncLet_finishImpl(SWIFT_ASYNC_CONTEXT AsyncContext *callerCo
436436
aletContext->alet = alet;
437437
aletContext->resultBuffer = reinterpret_cast<OpaqueValue*>(resultBuffer);
438438
auto futureContext = asImpl(alet)->getFutureContext();
439-
439+
440440
// TODO: It would be nice if we could await the future without having to
441441
// provide a buffer to store the value to, since we're going to dispose of
442442
// it anyway.
@@ -504,7 +504,7 @@ static void _asyncLet_consume_throwing_continuation(
504504
// Get the async let pointer so we can destroy the task.
505505
auto continuationContext = static_cast<AsyncLetContinuationContext*>(callContext);
506506
auto alet = continuationContext->alet;
507-
507+
508508
return asyncLet_finish_after_task_completion(callContext->Parent,
509509
alet,
510510
callContext->ResumeParent,
@@ -528,14 +528,14 @@ static void swift_asyncLet_consume_throwingImpl(
528528
callContext,
529529
nullptr);
530530
}
531-
531+
532532
auto aletContext = static_cast<AsyncLetContinuationContext*>(callContext);
533533
aletContext->ResumeParent
534534
= reinterpret_cast<TaskContinuationFunction*>(resumeFunction);
535535
aletContext->Parent = callerContext;
536536
aletContext->alet = alet;
537537
auto futureContext = asImpl(alet)->getFutureContext();
538-
538+
539539
// Unlike the non-throwing variant, whether we end up with a result depends
540540
// on the success of the task. If we raise an error, then the result buffer
541541
// will not be populated. Save the async let binding so we can fetch it

stdlib/public/Concurrency/Task.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@ FutureFragment::Status AsyncTask::waitFuture(AsyncTask *waitingTask,
117117
// NOTE: this acquire synchronizes with `completeFuture`.
118118
auto queueHead = fragment->waitQueue.load(std::memory_order_acquire);
119119
bool contextInitialized = false;
120-
auto escalatedPriority = JobPriority::Unspecified;
121120
while (true) {
122121
switch (queueHead.getStatus()) {
123122
case Status::Error:
@@ -192,7 +191,6 @@ FutureFragment::Status AsyncTask::waitFuture(AsyncTask *waitingTask,
192191
void NullaryContinuationJob::process(Job *_job) {
193192
auto *job = cast<NullaryContinuationJob>(_job);
194193

195-
auto *task = job->Task;
196194
auto *continuation = job->Continuation;
197195

198196
delete job;
@@ -1496,7 +1494,7 @@ swift_task_addCancellationHandlerImpl(
14961494

14971495
bool fireHandlerNow = false;
14981496

1499-
addStatusRecord(record, [&](ActiveTaskStatus oldStatus, ActiveTaskStatus& newStatus) {
1497+
addStatusRecordToSelf(record, [&](ActiveTaskStatus oldStatus, ActiveTaskStatus& newStatus) {
15001498
if (oldStatus.isCancelled()) {
15011499
fireHandlerNow = true;
15021500
// We don't fire the cancellation handler here since this function needs

stdlib/public/Concurrency/TaskGroup.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -921,7 +921,7 @@ static void swift_taskGroup_initializeWithFlagsImpl(size_t rawGroupFlags,
921921
assert(record->getKind() == swift::TaskStatusRecordKind::TaskGroup);
922922

923923
// ok, now that the group actually is initialized: attach it to the task
924-
addStatusRecord(record, [&](ActiveTaskStatus oldStatus, ActiveTaskStatus& newStatus) {
924+
addStatusRecordToSelf(record, [&](ActiveTaskStatus oldStatus, ActiveTaskStatus& newStatus) {
925925
// If the task has already been cancelled, reflect that immediately in
926926
// the group's status.
927927
if (oldStatus.isCancelled()) {

stdlib/public/Concurrency/TaskPrivate.h

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,13 @@ void removeStatusRecord(AsyncTask *task, TaskStatusRecord *record,
147147
SWIFT_CC(swift)
148148
void removeStatusRecord(TaskStatusRecord *record);
149149

150-
/// Add a status record to the current task. This must be called synchronously
151-
/// with the task.
150+
/// Add a status record to the input task.
151+
///
152+
/// Clients can optionally pass in the status of the task if they have already
153+
/// done the load on it already or if they require the oldStatus on the task
154+
/// prior to the atomic ActiveTaskStatus update that addStatusRecord will
155+
/// perform. This status will be updated with the last status on the task prior
156+
/// to updating it with the new status if the input function_ref allows so.
152157
///
153158
/// This function also takes in a function_ref which is given the old
154159
/// ActiveTaskStatus on the task and a reference to the new ActiveTaskStatus
@@ -162,9 +167,27 @@ void removeStatusRecord(TaskStatusRecord *record);
162167
/// If the function_ref returns false, the status record is not added to the
163168
/// task. This function_ref may be called multiple times and must be idempotent.
164169
/// The new status passed to `fn` is freshly derived from the current status and
165-
/// does not include modifications made by previous runs through the loop
170+
/// does not include modifications made by previous runs through the loop.
171+
///
172+
/// A convenience overload is also provided for clients who have not done the
173+
/// load on the task status prior to adding the record
166174
SWIFT_CC(swift)
167-
bool addStatusRecord(TaskStatusRecord *record,
175+
bool addStatusRecord(AsyncTask *task, TaskStatusRecord *record,
176+
llvm::function_ref<bool(ActiveTaskStatus, ActiveTaskStatus&)> testAddRecord);
177+
178+
SWIFT_CC(swift)
179+
bool addStatusRecord(AsyncTask *task, TaskStatusRecord *record,
180+
ActiveTaskStatus& taskStatus,
181+
llvm::function_ref<bool(ActiveTaskStatus, ActiveTaskStatus&)> testAddRecord);
182+
183+
/// Convenience functions for clients who want to just add a record to the task
184+
/// on the current thread
185+
SWIFT_CC(swift)
186+
bool addStatusRecordToSelf(TaskStatusRecord *record,
187+
llvm::function_ref<bool(ActiveTaskStatus, ActiveTaskStatus&)> testAddRecord);
188+
189+
SWIFT_CC(swift)
190+
bool addStatusRecordToSelf(TaskStatusRecord *record, ActiveTaskStatus& taskStatus,
168191
llvm::function_ref<bool(ActiveTaskStatus, ActiveTaskStatus&)> testAddRecord);
169192

170193
/// A helper function for updating a new child task that is created with
@@ -958,17 +981,13 @@ void AsyncTask::flagAsSuspended(TaskDependencyStatusRecord *dependencyStatusReco
958981
dependencyStatusRecord);
959982
assert(dependencyStatusRecord != NULL);
960983

961-
bool taskWasEscalated = false;
962-
JobPriority taskStoredPriority = JobPriority::Unspecified;
984+
auto oldStatus = _private()._status().load(std::memory_order_relaxed);
985+
// We can only be suspended if we were previously running. See state
986+
// transitions listed out in Task.h
987+
assert(oldStatus.isRunning() && !oldStatus.isEnqueued());
963988

964-
addStatusRecord(dependencyStatusRecord, [&](ActiveTaskStatus oldStatus,
989+
addStatusRecord(this, dependencyStatusRecord, oldStatus, [&](ActiveTaskStatus unused,
965990
ActiveTaskStatus &newStatus) {
966-
// We can only be suspended if we were previously running. See state
967-
// transitions listed out in Task.h
968-
assert(oldStatus.isRunning() && !oldStatus.isEnqueued());
969-
taskWasEscalated = oldStatus.isStoredPriorityEscalated();
970-
taskStoredPriority = oldStatus.getStoredPriority();
971-
972991
newStatus = newStatus.withRunning(false);
973992
newStatus = newStatus.withoutStoredPriorityEscalation();
974993
newStatus = newStatus.withTaskDependency();
@@ -985,7 +1004,7 @@ void AsyncTask::flagAsSuspended(TaskDependencyStatusRecord *dependencyStatusReco
9851004
// published it in the ActiveTaskStatus.
9861005
SWIFT_TASK_DEBUG_LOG("[Dependency] Escalate the dependency %p of task %p",
9871006
dependencyStatusRecord, this);
988-
dependencyStatusRecord->performEscalationAction(taskStoredPriority);
1007+
dependencyStatusRecord->performEscalationAction(newStatus.getStoredPriority());
9891008

9901009
// Always add the dependency status record
9911010
return true;
@@ -994,10 +1013,10 @@ void AsyncTask::flagAsSuspended(TaskDependencyStatusRecord *dependencyStatusReco
9941013
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
9951014
// Successfully dropped task drain lock, make sure to remove override on
9961015
// thread due to task
997-
if (taskWasEscalated) {
1016+
if (oldStatus.isStoredPriorityEscalated()) {
9981017
SWIFT_TASK_DEBUG_LOG("[Override] Reset override %#x on thread from task %p",
999-
taskStoredPriority, this);
1000-
swift_dispatch_lock_override_end((qos_class_t) taskStoredPriority);
1018+
oldStatus.getStoredPriority(), this);
1019+
swift_dispatch_lock_override_end((qos_class_t) oldStatus.getStoredPriority());
10011020
}
10021021
#endif
10031022

0 commit comments

Comments
 (0)