Skip to content

Commit

Permalink
Merge pull request #40606 from apple/rokhinip/86347801-task-creation-…
Browse files Browse the repository at this point in the history
…escalation-race

Resolve race between task creation and concurrent escalation and cancellation.
  • Loading branch information
rokhinip committed Jan 12, 2022
2 parents 5da4d80 + e35eba0 commit a4fe57f
Show file tree
Hide file tree
Showing 11 changed files with 143 additions and 311 deletions.
6 changes: 3 additions & 3 deletions include/swift/ABI/TaskStatus.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ namespace swift {
/// access by a cancelling thread. In particular, the chain of
/// status records must not be disturbed. When the task leaves
/// the scope that requires the status record, the record can
/// be unregistered from the task with `swift_task_removeStatusRecord`,
/// be unregistered from the task with `removeStatusRecord`,
/// at which point the memory can be returned to the system.
class TaskStatusRecord {
public:
Expand Down Expand Up @@ -256,7 +256,7 @@ class TaskGroupTaskStatusRecord : public TaskStatusRecord {
///
/// The end of any call to the function will be ordered before the
/// end of a call to unregister this record from the task. That is,
/// code may call `swift_task_removeStatusRecord` and freely
/// code may call `removeStatusRecord` and freely
/// assume after it returns that this function will not be
/// subsequently used.
class CancellationNotificationStatusRecord : public TaskStatusRecord {
Expand Down Expand Up @@ -284,7 +284,7 @@ class CancellationNotificationStatusRecord : public TaskStatusRecord {
///
/// The end of any call to the function will be ordered before the
/// end of a call to unregister this record from the task. That is,
/// code may call `swift_task_removeStatusRecord` and freely
/// code may call `removeStatusRecord` and freely
/// assume after it returns that this function will not be
/// subsequently used.
class EscalationNotificationStatusRecord : public TaskStatusRecord {
Expand Down
48 changes: 1 addition & 47 deletions include/swift/Runtime/Concurrency.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
#ifndef SWIFT_RUNTIME_CONCURRENCY_H
#define SWIFT_RUNTIME_CONCURRENCY_H

#include "swift/ABI/AsyncLet.h"
#include "swift/ABI/Task.h"
#include "swift/ABI/TaskGroup.h"
#include "swift/ABI/AsyncLet.h"
#include "swift/ABI/TaskStatus.h"

#pragma clang diagnostic push
Expand Down Expand Up @@ -466,40 +466,6 @@ void swift_asyncLet_consume_throwing(SWIFT_ASYNC_CONTEXT AsyncContext *,
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
bool swift_taskGroup_hasTaskGroupRecord();

/// Add a status record to a task. The record should not be
/// modified while it is registered with a task.
///
/// This must be called synchronously with the task.
///
/// If the task is already cancelled, returns `false` but still adds
/// the status record.
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
bool swift_task_addStatusRecord(TaskStatusRecord *record);

/// Add a status record to a task if the task has not already
/// been cancelled. The record should not be modified while it is
/// registered with a task.
///
/// This must be called synchronously with the task.
///
/// If the task is already cancelled, returns `false` and does not
/// add the status record.
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
bool swift_task_tryAddStatusRecord(TaskStatusRecord *record);

/// Remove a status record from a task. After this call returns,
/// the record's memory can be freely modified or deallocated.
///
/// This must be called synchronously with the task. The record must
/// be registered with the task or else this may crash.
///
/// The given record need not be the last record added to
/// the task, but the operation may be less efficient if not.
///
/// Returns false if the task has been cancelled.
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
bool swift_task_removeStatusRecord(TaskStatusRecord *record);

/// Signifies whether the current task is in the middle of executing the
/// operation block of a `with(Throwing)TaskGroup(...) { <operation> }`.
///
Expand All @@ -509,18 +475,6 @@ bool swift_task_removeStatusRecord(TaskStatusRecord *record);
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
bool swift_task_hasTaskGroupStatusRecord();

/// Attach a child task to its parent task and return the newly created
/// `ChildTaskStatusRecord`.
///
/// The record must be removed with by the parent invoking
/// `swift_task_detachChild` when the child has completed.
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
ChildTaskStatusRecord* swift_task_attachChild(AsyncTask *child);

/// Remove a child task from the parent tracking it.
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
void swift_task_detachChild(ChildTaskStatusRecord *record);

SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
size_t swift_task_getJobFlags(AsyncTask* task);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,30 +308,10 @@ OVERRIDE_TASK_LOCAL(task_localsCopyTo, void,
(AsyncTask *target),
(target))

OVERRIDE_TASK_STATUS(task_addStatusRecord, bool,
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift),
swift::, (TaskStatusRecord *newRecord), (newRecord))

OVERRIDE_TASK_STATUS(task_tryAddStatusRecord, bool,
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift),
swift::, (TaskStatusRecord *newRecord), (newRecord))

OVERRIDE_TASK_STATUS(task_removeStatusRecord, bool,
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift),
swift::, (TaskStatusRecord *record), (record))

OVERRIDE_TASK_STATUS(task_hasTaskGroupStatusRecord, bool,
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift),
swift::, , )

OVERRIDE_TASK_STATUS(task_attachChild, ChildTaskStatusRecord *,
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift),
swift::, (AsyncTask *child), (child))

OVERRIDE_TASK_STATUS(task_detachChild, void,
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift),
swift::, (ChildTaskStatusRecord *record), (record))

OVERRIDE_TASK_STATUS(task_cancel, void, SWIFT_EXPORT_FROM(swift_Concurrency),
SWIFT_CC(swift), swift::, (AsyncTask *task), (task))

Expand Down
14 changes: 10 additions & 4 deletions stdlib/public/Concurrency/AsyncLet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,14 @@ void swift::asyncLet_addImpl(AsyncTask *task, AsyncLet *asyncLet,
auto record = impl->getTaskRecord();
assert(impl == record && "the async-let IS the task record");

// ok, now that the group actually is initialized: attach it to the task
swift_task_addStatusRecord(record);
// ok, now that the async let task actually is initialized: attach it to the
// current task
bool addedRecord =
addStatusRecord(record, [&](ActiveTaskStatus parentStatus) {
updateNewChildWithParentAndGroupState(task, parentStatus, NULL);
return true;
});
assert(addedRecord);
}

// =============================================================================
Expand Down Expand Up @@ -309,7 +315,7 @@ static void swift_asyncLet_endImpl(AsyncLet *alet) {

// Remove the child record from the parent task
auto record = asImpl(alet)->getTaskRecord();
swift_task_removeStatusRecord(record);
removeStatusRecord(record);

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

Expand Down Expand Up @@ -337,7 +343,7 @@ static void asyncLet_finish_after_task_completion(SWIFT_ASYNC_CONTEXT AsyncConte

// Remove the child record from the parent task
auto record = asImpl(alet)->getTaskRecord();
swift_task_removeStatusRecord(record);
removeStatusRecord(record);

// and finally, release the task and destroy the async-let
assert(swift_task_getCurrent() && "async-let must have a parent task");
Expand Down
20 changes: 14 additions & 6 deletions stdlib/public/Concurrency/Task.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1089,19 +1089,27 @@ swift_task_addCancellationHandlerImpl(
auto *record = new (allocation)
CancellationNotificationStatusRecord(unsigned_handler, context);

if (swift_task_addStatusRecord(record))
return record;
bool fireHandlerNow = false;

// else, the task was already cancelled, so while the record was added,
// we must run it immediately here since no other task will trigger it.
record->run();
addStatusRecord(record, [&](ActiveTaskStatus parentStatus) {
if (parentStatus.isCancelled()) {
fireHandlerNow = true;
// We don't fire the cancellation handler here since this function needs
// to be idempotent
}
return true;
});

if (fireHandlerNow) {
record->run();
}
return record;
}

SWIFT_CC(swift)
static void swift_task_removeCancellationHandlerImpl(
CancellationNotificationStatusRecord *record) {
swift_task_removeStatusRecord(record);
removeStatusRecord(record);
swift_task_dealloc(record);
}

Expand Down
15 changes: 9 additions & 6 deletions stdlib/public/Concurrency/TaskGroup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -474,11 +474,14 @@ static void swift_taskGroup_initializeImpl(TaskGroup *group, const Metadata *T)
assert(impl == record && "the group IS the task record");

// ok, now that the group actually is initialized: attach it to the task
bool notCancelled = swift_task_addStatusRecord(record);

// If the task has already been cancelled, reflect that immediately in
// the group status.
if (!notCancelled) impl->statusCancel();
addStatusRecord(record, [&](ActiveTaskStatus parentStatus) {
// If the task has already been cancelled, reflect that immediately in
// the group's status.
if (parentStatus.isCancelled()) {
impl->statusCancel();
}
return true;
});
}

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

// First, remove the group from the task and deallocate the record
swift_task_removeStatusRecord(getTaskRecord());
removeStatusRecord(getTaskRecord());

// No need to drain our queue here, as by the time we call destroy,
// all tasks inside the group must have been awaited on already.
Expand Down
44 changes: 44 additions & 0 deletions stdlib/public/Concurrency/TaskPrivate.h
Original file line number Diff line number Diff line change
Expand Up @@ -239,12 +239,23 @@ class alignas(sizeof(void*) * 2) ActiveTaskStatus {
bool isStoredPriorityEscalated() const {
return Flags & IsEscalated;
}

/// Creates a new active task status for a task with the specified priority
/// and masks away any existing priority related flags on the task status. All
/// other flags about the task are unmodified. This is only safe to use to
/// generate an initial task status for a new task that is not yet running.
ActiveTaskStatus withNewPriority(JobPriority priority) const {
return ActiveTaskStatus(Record,
(Flags & ~PriorityMask) | uintptr_t(priority));
}

ActiveTaskStatus withEscalatedPriority(JobPriority priority) const {
assert(priority > getStoredPriority());
return ActiveTaskStatus(Record,
(Flags & ~PriorityMask)
| IsEscalated | uintptr_t(priority));
}

ActiveTaskStatus withoutStoredPriorityEscalation() const {
assert(isStoredPriorityEscalated());
return ActiveTaskStatus(Record, Flags & ~IsEscalated);
Expand Down Expand Up @@ -447,6 +458,39 @@ inline bool AsyncTask::localValuePop() {
return _private().Local.popValue(this);
}

/*************** Methods for Status records manipulation ******************/

/// Remove a status record from a task. After this call returns,
/// the record's memory can be freely modified or deallocated.
///
/// This must be called synchronously with the task. The record must
/// be registered with the task or else this may crash.
///
/// The given record need not be the last record added to
/// the task, but the operation may be less efficient if not.
///
/// Returns false if the task has been cancelled.
SWIFT_CC(swift)
bool removeStatusRecord(TaskStatusRecord *record);

/// Add a status record to a task. This must be called synchronously with the
/// task.
///
/// This function also takes in a function_ref which is given the task status of
/// the task we're adding the record to, to determine if the current status of
/// the task permits adding the status record. This function_ref may be called
/// multiple times and must be idempotent.
SWIFT_CC(swift)
bool addStatusRecord(TaskStatusRecord *record,
llvm::function_ref<bool(ActiveTaskStatus)> testAddRecord);

/// A helper function for updating a new child task that is created with
/// information from the parent or the group that it was going to be added to.
SWIFT_CC(swift)
void updateNewChildWithParentAndGroupState(AsyncTask *child,
ActiveTaskStatus parentStatus,
TaskGroup *group);

} // end namespace swift

#endif
Loading

0 comments on commit a4fe57f

Please sign in to comment.