Skip to content

Commit a4fe57f

Browse files
authored
Merge pull request #40606 from apple/rokhinip/86347801-task-creation-escalation-race
Resolve race between task creation and concurrent escalation and cancellation.
2 parents 5da4d80 + e35eba0 commit a4fe57f

File tree

11 files changed

+143
-311
lines changed

11 files changed

+143
-311
lines changed

include/swift/ABI/TaskStatus.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ namespace swift {
3535
/// access by a cancelling thread. In particular, the chain of
3636
/// status records must not be disturbed. When the task leaves
3737
/// the scope that requires the status record, the record can
38-
/// be unregistered from the task with `swift_task_removeStatusRecord`,
38+
/// be unregistered from the task with `removeStatusRecord`,
3939
/// at which point the memory can be returned to the system.
4040
class TaskStatusRecord {
4141
public:
@@ -256,7 +256,7 @@ class TaskGroupTaskStatusRecord : public TaskStatusRecord {
256256
///
257257
/// The end of any call to the function will be ordered before the
258258
/// end of a call to unregister this record from the task. That is,
259-
/// code may call `swift_task_removeStatusRecord` and freely
259+
/// code may call `removeStatusRecord` and freely
260260
/// assume after it returns that this function will not be
261261
/// subsequently used.
262262
class CancellationNotificationStatusRecord : public TaskStatusRecord {
@@ -284,7 +284,7 @@ class CancellationNotificationStatusRecord : public TaskStatusRecord {
284284
///
285285
/// The end of any call to the function will be ordered before the
286286
/// end of a call to unregister this record from the task. That is,
287-
/// code may call `swift_task_removeStatusRecord` and freely
287+
/// code may call `removeStatusRecord` and freely
288288
/// assume after it returns that this function will not be
289289
/// subsequently used.
290290
class EscalationNotificationStatusRecord : public TaskStatusRecord {

include/swift/Runtime/Concurrency.h

Lines changed: 1 addition & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
#ifndef SWIFT_RUNTIME_CONCURRENCY_H
1818
#define SWIFT_RUNTIME_CONCURRENCY_H
1919

20+
#include "swift/ABI/AsyncLet.h"
2021
#include "swift/ABI/Task.h"
2122
#include "swift/ABI/TaskGroup.h"
22-
#include "swift/ABI/AsyncLet.h"
2323
#include "swift/ABI/TaskStatus.h"
2424

2525
#pragma clang diagnostic push
@@ -466,40 +466,6 @@ void swift_asyncLet_consume_throwing(SWIFT_ASYNC_CONTEXT AsyncContext *,
466466
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
467467
bool swift_taskGroup_hasTaskGroupRecord();
468468

469-
/// Add a status record to a task. The record should not be
470-
/// modified while it is registered with a task.
471-
///
472-
/// This must be called synchronously with the task.
473-
///
474-
/// If the task is already cancelled, returns `false` but still adds
475-
/// the status record.
476-
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
477-
bool swift_task_addStatusRecord(TaskStatusRecord *record);
478-
479-
/// Add a status record to a task if the task has not already
480-
/// been cancelled. The record should not be modified while it is
481-
/// registered with a task.
482-
///
483-
/// This must be called synchronously with the task.
484-
///
485-
/// If the task is already cancelled, returns `false` and does not
486-
/// add the status record.
487-
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
488-
bool swift_task_tryAddStatusRecord(TaskStatusRecord *record);
489-
490-
/// Remove a status record from a task. After this call returns,
491-
/// the record's memory can be freely modified or deallocated.
492-
///
493-
/// This must be called synchronously with the task. The record must
494-
/// be registered with the task or else this may crash.
495-
///
496-
/// The given record need not be the last record added to
497-
/// the task, but the operation may be less efficient if not.
498-
///
499-
/// Returns false if the task has been cancelled.
500-
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
501-
bool swift_task_removeStatusRecord(TaskStatusRecord *record);
502-
503469
/// Signifies whether the current task is in the middle of executing the
504470
/// operation block of a `with(Throwing)TaskGroup(...) { <operation> }`.
505471
///
@@ -509,18 +475,6 @@ bool swift_task_removeStatusRecord(TaskStatusRecord *record);
509475
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
510476
bool swift_task_hasTaskGroupStatusRecord();
511477

512-
/// Attach a child task to its parent task and return the newly created
513-
/// `ChildTaskStatusRecord`.
514-
///
515-
/// The record must be removed with by the parent invoking
516-
/// `swift_task_detachChild` when the child has completed.
517-
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
518-
ChildTaskStatusRecord* swift_task_attachChild(AsyncTask *child);
519-
520-
/// Remove a child task from the parent tracking it.
521-
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
522-
void swift_task_detachChild(ChildTaskStatusRecord *record);
523-
524478
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
525479
size_t swift_task_getJobFlags(AsyncTask* task);
526480

stdlib/public/CompatibilityOverride/CompatibilityOverrideConcurrency.def

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -308,30 +308,10 @@ OVERRIDE_TASK_LOCAL(task_localsCopyTo, void,
308308
(AsyncTask *target),
309309
(target))
310310

311-
OVERRIDE_TASK_STATUS(task_addStatusRecord, bool,
312-
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift),
313-
swift::, (TaskStatusRecord *newRecord), (newRecord))
314-
315-
OVERRIDE_TASK_STATUS(task_tryAddStatusRecord, bool,
316-
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift),
317-
swift::, (TaskStatusRecord *newRecord), (newRecord))
318-
319-
OVERRIDE_TASK_STATUS(task_removeStatusRecord, bool,
320-
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift),
321-
swift::, (TaskStatusRecord *record), (record))
322-
323311
OVERRIDE_TASK_STATUS(task_hasTaskGroupStatusRecord, bool,
324312
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift),
325313
swift::, , )
326314

327-
OVERRIDE_TASK_STATUS(task_attachChild, ChildTaskStatusRecord *,
328-
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift),
329-
swift::, (AsyncTask *child), (child))
330-
331-
OVERRIDE_TASK_STATUS(task_detachChild, void,
332-
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift),
333-
swift::, (ChildTaskStatusRecord *record), (record))
334-
335315
OVERRIDE_TASK_STATUS(task_cancel, void, SWIFT_EXPORT_FROM(swift_Concurrency),
336316
SWIFT_CC(swift), swift::, (AsyncTask *task), (task))
337317

stdlib/public/Concurrency/AsyncLet.cpp

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,14 @@ void swift::asyncLet_addImpl(AsyncTask *task, AsyncLet *asyncLet,
142142
auto record = impl->getTaskRecord();
143143
assert(impl == record && "the async-let IS the task record");
144144

145-
// ok, now that the group actually is initialized: attach it to the task
146-
swift_task_addStatusRecord(record);
145+
// ok, now that the async let task actually is initialized: attach it to the
146+
// current task
147+
bool addedRecord =
148+
addStatusRecord(record, [&](ActiveTaskStatus parentStatus) {
149+
updateNewChildWithParentAndGroupState(task, parentStatus, NULL);
150+
return true;
151+
});
152+
assert(addedRecord);
147153
}
148154

149155
// =============================================================================
@@ -309,7 +315,7 @@ static void swift_asyncLet_endImpl(AsyncLet *alet) {
309315

310316
// Remove the child record from the parent task
311317
auto record = asImpl(alet)->getTaskRecord();
312-
swift_task_removeStatusRecord(record);
318+
removeStatusRecord(record);
313319

314320
// TODO: we need to implicitly await either before the end or here somehow.
315321

@@ -337,7 +343,7 @@ static void asyncLet_finish_after_task_completion(SWIFT_ASYNC_CONTEXT AsyncConte
337343

338344
// Remove the child record from the parent task
339345
auto record = asImpl(alet)->getTaskRecord();
340-
swift_task_removeStatusRecord(record);
346+
removeStatusRecord(record);
341347

342348
// and finally, release the task and destroy the async-let
343349
assert(swift_task_getCurrent() && "async-let must have a parent task");

stdlib/public/Concurrency/Task.cpp

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,19 +1089,27 @@ swift_task_addCancellationHandlerImpl(
10891089
auto *record = new (allocation)
10901090
CancellationNotificationStatusRecord(unsigned_handler, context);
10911091

1092-
if (swift_task_addStatusRecord(record))
1093-
return record;
1092+
bool fireHandlerNow = false;
10941093

1095-
// else, the task was already cancelled, so while the record was added,
1096-
// we must run it immediately here since no other task will trigger it.
1097-
record->run();
1094+
addStatusRecord(record, [&](ActiveTaskStatus parentStatus) {
1095+
if (parentStatus.isCancelled()) {
1096+
fireHandlerNow = true;
1097+
// We don't fire the cancellation handler here since this function needs
1098+
// to be idempotent
1099+
}
1100+
return true;
1101+
});
1102+
1103+
if (fireHandlerNow) {
1104+
record->run();
1105+
}
10981106
return record;
10991107
}
11001108

11011109
SWIFT_CC(swift)
11021110
static void swift_task_removeCancellationHandlerImpl(
11031111
CancellationNotificationStatusRecord *record) {
1104-
swift_task_removeStatusRecord(record);
1112+
removeStatusRecord(record);
11051113
swift_task_dealloc(record);
11061114
}
11071115

stdlib/public/Concurrency/TaskGroup.cpp

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -474,11 +474,14 @@ static void swift_taskGroup_initializeImpl(TaskGroup *group, const Metadata *T)
474474
assert(impl == record && "the group IS the task record");
475475

476476
// ok, now that the group actually is initialized: attach it to the task
477-
bool notCancelled = swift_task_addStatusRecord(record);
478-
479-
// If the task has already been cancelled, reflect that immediately in
480-
// the group status.
481-
if (!notCancelled) impl->statusCancel();
477+
addStatusRecord(record, [&](ActiveTaskStatus parentStatus) {
478+
// If the task has already been cancelled, reflect that immediately in
479+
// the group's status.
480+
if (parentStatus.isCancelled()) {
481+
impl->statusCancel();
482+
}
483+
return true;
484+
});
482485
}
483486

484487
// =============================================================================
@@ -505,7 +508,7 @@ void TaskGroupImpl::destroy() {
505508
SWIFT_TASK_DEBUG_LOG("destroying task group = %p", this);
506509

507510
// First, remove the group from the task and deallocate the record
508-
swift_task_removeStatusRecord(getTaskRecord());
511+
removeStatusRecord(getTaskRecord());
509512

510513
// No need to drain our queue here, as by the time we call destroy,
511514
// all tasks inside the group must have been awaited on already.

stdlib/public/Concurrency/TaskPrivate.h

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,12 +239,23 @@ class alignas(sizeof(void*) * 2) ActiveTaskStatus {
239239
bool isStoredPriorityEscalated() const {
240240
return Flags & IsEscalated;
241241
}
242+
243+
/// Creates a new active task status for a task with the specified priority
244+
/// and masks away any existing priority related flags on the task status. All
245+
/// other flags about the task are unmodified. This is only safe to use to
246+
/// generate an initial task status for a new task that is not yet running.
247+
ActiveTaskStatus withNewPriority(JobPriority priority) const {
248+
return ActiveTaskStatus(Record,
249+
(Flags & ~PriorityMask) | uintptr_t(priority));
250+
}
251+
242252
ActiveTaskStatus withEscalatedPriority(JobPriority priority) const {
243253
assert(priority > getStoredPriority());
244254
return ActiveTaskStatus(Record,
245255
(Flags & ~PriorityMask)
246256
| IsEscalated | uintptr_t(priority));
247257
}
258+
248259
ActiveTaskStatus withoutStoredPriorityEscalation() const {
249260
assert(isStoredPriorityEscalated());
250261
return ActiveTaskStatus(Record, Flags & ~IsEscalated);
@@ -447,6 +458,39 @@ inline bool AsyncTask::localValuePop() {
447458
return _private().Local.popValue(this);
448459
}
449460

461+
/*************** Methods for Status records manipulation ******************/
462+
463+
/// Remove a status record from a task. After this call returns,
464+
/// the record's memory can be freely modified or deallocated.
465+
///
466+
/// This must be called synchronously with the task. The record must
467+
/// be registered with the task or else this may crash.
468+
///
469+
/// The given record need not be the last record added to
470+
/// the task, but the operation may be less efficient if not.
471+
///
472+
/// Returns false if the task has been cancelled.
473+
SWIFT_CC(swift)
474+
bool removeStatusRecord(TaskStatusRecord *record);
475+
476+
/// Add a status record to a task. This must be called synchronously with the
477+
/// task.
478+
///
479+
/// This function also takes in a function_ref which is given the task status of
480+
/// the task we're adding the record to, to determine if the current status of
481+
/// the task permits adding the status record. This function_ref may be called
482+
/// multiple times and must be idempotent.
483+
SWIFT_CC(swift)
484+
bool addStatusRecord(TaskStatusRecord *record,
485+
llvm::function_ref<bool(ActiveTaskStatus)> testAddRecord);
486+
487+
/// A helper function for updating a new child task that is created with
488+
/// information from the parent or the group that it was going to be added to.
489+
SWIFT_CC(swift)
490+
void updateNewChildWithParentAndGroupState(AsyncTask *child,
491+
ActiveTaskStatus parentStatus,
492+
TaskGroup *group);
493+
450494
} // end namespace swift
451495

452496
#endif

0 commit comments

Comments
 (0)