Skip to content

Resolve race between task creation and concurrent escalation and cancellation. #40606

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -1056,19 +1056,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 @@ -238,12 +238,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 @@ -438,6 +449,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