Skip to content

Commit 7e3e03d

Browse files
committed
Change the logic for adding new task status records to a task
This change has two parts to it: 1. Add in a new interface (addStatusRecordWithChecks) for adding task status records that also takes in a function ref. This function ref will be used to evaluate if current state of the parent task has any changes that need to be propagated to the child task that has been created. This is necessary to prevent the following race between task creation and concurrent cancellation and escalation: a. Parent task create child task. It does lazy relaxed loads on its own state while doing so and propagates this state to the child. b. Child task is created but has not been attached to the parent task/task group. c. Parent task gets cancelled by another thread. d. Child task gets linked into the parent’s task status records but no reevaluation has happened to account for changes that might have happened to the parent after (a). 2. Move status record management functions from the Runtime/Concurrency.h to TaskPrivate.h. Remove any corresponding overrides that are no longer needed. Remove unused tryAddStatusRecord method whose functionality is provided by addStatusRecordWithChecks. Radar-Id: rdar://problem/86347801
1 parent 9965df7 commit 7e3e03d

File tree

10 files changed

+199
-146
lines changed

10 files changed

+199
-146
lines changed

include/swift/Runtime/Concurrency.h

Lines changed: 1 addition & 35 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
///

stdlib/public/CompatibilityOverride/CompatibilityOverrideConcurrency.def

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -308,18 +308,6 @@ 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::, , )

stdlib/public/Concurrency/AsyncLet.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,15 @@ 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 = swift_task_addStatusRecordWithChecks(
148+
record, [&](ActiveTaskStatus parentStatus) {
149+
swift_task_updateNewChildWithParentAndGroupState(task, parentStatus,
150+
NULL);
151+
return true;
152+
});
153+
assert(addedRecord);
147154
}
148155

149156
// =============================================================================

stdlib/public/Concurrency/Task.cpp

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,12 +1056,22 @@ swift_task_addCancellationHandlerImpl(
10561056
auto *record = new (allocation)
10571057
CancellationNotificationStatusRecord(unsigned_handler, context);
10581058

1059-
if (swift_task_addStatusRecord(record))
1060-
return record;
1061-
1062-
// else, the task was already cancelled, so while the record was added,
1063-
// we must run it immediately here since no other task will trigger it.
1064-
record->run();
1059+
bool fireHandlerNow = false;
1060+
1061+
swift_task_addStatusRecordWithChecks(record,
1062+
[&](ActiveTaskStatus parentStatus) {
1063+
if (parentStatus.isCancelled()) {
1064+
fireHandlerNow = true;
1065+
/* We don't fire the cancellation
1066+
* handler here since this function
1067+
* needs to be idempotent */
1068+
}
1069+
return true;
1070+
});
1071+
1072+
if (fireHandlerNow) {
1073+
record->run();
1074+
}
10651075
return record;
10661076
}
10671077

stdlib/public/Concurrency/TaskGroup.cpp

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -470,11 +470,13 @@ static void swift_taskGroup_initializeImpl(TaskGroup *group, const Metadata *T)
470470
assert(impl == record && "the group IS the task record");
471471

472472
// ok, now that the group actually is initialized: attach it to the task
473-
bool notCancelled = swift_task_addStatusRecord(record);
474-
475-
// If the task has already been cancelled, reflect that immediately in
476-
// the group status.
477-
if (!notCancelled) impl->statusCancel();
473+
swift_task_addStatusRecordWithChecks(record,
474+
[&](ActiveTaskStatus parentStatus) {
475+
if (parentStatus.isCancelled()) {
476+
impl->statusCancel();
477+
}
478+
return true;
479+
});
478480
}
479481

480482
// =============================================================================

stdlib/public/Concurrency/TaskPrivate.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,40 @@ inline bool AsyncTask::localValuePop() {
438438
return _private().Local.popValue(this);
439439
}
440440

441+
/*************** Methods for Status records manipulation ******************/
442+
443+
/// Remove a status record from a task. After this call returns,
444+
/// the record's memory can be freely modified or deallocated.
445+
///
446+
/// This must be called synchronously with the task. The record must
447+
/// be registered with the task or else this may crash.
448+
///
449+
/// The given record need not be the last record added to
450+
/// the task, but the operation may be less efficient if not.
451+
///
452+
/// Returns false if the task has been cancelled.
453+
SWIFT_EXPORT_FROM(swift_Concurrency)
454+
SWIFT_CC(swift) bool swift_task_removeStatusRecord(TaskStatusRecord *record);
455+
456+
/// Add a status record to a task. This must be called synchronously with the
457+
/// task.
458+
///
459+
/// This function also takes in a function_ref which is given the task status of
460+
/// the task we're adding the record to, to determine if the current status of
461+
/// the task permits adding the status record. This function_ref may be called
462+
/// multiple times and must be idempotent.
463+
SWIFT_EXPORT_FROM(swift_Concurrency)
464+
SWIFT_CC(swift)
465+
bool swift_task_addStatusRecordWithChecks(
466+
TaskStatusRecord *record,
467+
llvm::function_ref<bool(ActiveTaskStatus)> testAddRecord);
468+
469+
/// A helper method for updating a new child task that is created with
470+
/// information from the parent or the group that it was going to be added to.
471+
SWIFT_EXPORT_FROM(swift_Concurrency)
472+
SWIFT_CC(swift)
473+
void swift_task_updateNewChildWithParentAndGroupState(
474+
AsyncTask *child, ActiveTaskStatus parentStatus, TaskGroup *group);
441475
} // end namespace swift
442476

443477
#endif

stdlib/public/Concurrency/TaskStatus.cpp

Lines changed: 82 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -256,11 +256,14 @@ static bool withStatusRecordLock(AsyncTask *task,
256256
/*************************** RECORD MANAGEMENT ****************************/
257257
/**************************************************************************/
258258

259+
SWIFT_EXPORT_FROM(swift_Concurrency)
259260
SWIFT_CC(swift)
260-
static bool swift_task_addStatusRecordImpl(TaskStatusRecord *newRecord) {
261-
auto task = swift_task_getCurrent();
261+
bool swift_task_addStatusRecordWithChecks(
262+
TaskStatusRecord *newRecord,
263+
llvm::function_ref<bool(ActiveTaskStatus status)> shouldAddRecord) {
262264

263-
// Load the current state. We can use a relaxed load because we're
265+
auto task = swift_task_getCurrent();
266+
// Load the current state. We can use a relaxed load because we're
264267
// synchronous with the task.
265268
auto oldStatus = task->_private().Status.load(std::memory_order_relaxed);
266269

@@ -273,53 +276,28 @@ static bool swift_task_addStatusRecordImpl(TaskStatusRecord *newRecord) {
273276
newRecord->resetParent(oldStatus.getInnermostRecord());
274277

275278
// Set the record as the new innermost record.
276-
// We have to use a release on success to make the initialization of
277-
// the new record visible to the cancelling thread.
278279
ActiveTaskStatus newStatus = oldStatus.withInnermostRecord(newRecord);
279-
if (task->_private().Status.compare_exchange_weak(oldStatus, newStatus,
280-
/*success*/ std::memory_order_release,
281-
/*failure*/ std::memory_order_relaxed))
282-
return !oldStatus.isCancelled();
283-
}
284-
}
285-
286-
SWIFT_CC(swift)
287-
static bool swift_task_tryAddStatusRecordImpl(TaskStatusRecord *newRecord) {
288-
auto task = swift_task_getCurrent();
289-
290-
// Load the current state. We can use a relaxed load because we're
291-
// synchronous with the task.
292-
auto oldStatus = task->_private().Status.load(std::memory_order_relaxed);
293280

294-
while (true) {
295-
// If the old info is already cancelled, do nothing.
296-
if (oldStatus.isCancelled())
281+
if (shouldAddRecord(newStatus)) {
282+
// We have to use a release on success to make the initialization of
283+
// the new record visible to the cancelling thread.
284+
if (task->_private().Status.compare_exchange_weak(
285+
oldStatus, newStatus,
286+
/*success*/ std::memory_order_release,
287+
/*failure*/ std::memory_order_relaxed)) {
288+
return true;
289+
} else {
290+
/* Retry */
291+
}
292+
} else {
297293
return false;
298-
299-
// Wait for any active lock to be released.
300-
if (oldStatus.isLocked()) {
301-
waitForStatusRecordUnlock(task, oldStatus);
302-
303-
if (oldStatus.isCancelled())
304-
return false;
305294
}
306-
307-
// Reset the parent of the new record.
308-
newRecord->resetParent(oldStatus.getInnermostRecord());
309-
310-
// Set the record as the new innermost record.
311-
// We have to use a release on success to make the initialization of
312-
// the new record visible to the cancelling thread.
313-
ActiveTaskStatus newStatus = oldStatus.withInnermostRecord(newRecord);
314-
if (task->_private().Status.compare_exchange_weak(oldStatus, newStatus,
315-
/*success*/ std::memory_order_release,
316-
/*failure*/ std::memory_order_relaxed))
317-
return true;
318295
}
319296
}
320297

298+
SWIFT_EXPORT_FROM(swift_Concurrency)
321299
SWIFT_CC(swift)
322-
static bool swift_task_removeStatusRecordImpl(TaskStatusRecord *record) {
300+
bool swift_task_removeStatusRecord(TaskStatusRecord *record) {
323301
auto task = swift_task_getCurrent();
324302
SWIFT_TASK_DEBUG_LOG("remove status record = %p, from current task = %p",
325303
record, task);
@@ -411,7 +389,14 @@ swift_task_attachChildImpl(AsyncTask *child) {
411389
auto record = new (allocation) swift::ChildTaskStatusRecord(child);
412390
SWIFT_TASK_DEBUG_LOG("attach child task = %p, record = %p, to current task = %p",
413391
child, record, swift_task_getCurrent());
414-
swift_task_addStatusRecord(record);
392+
393+
bool added_record = swift_task_addStatusRecordWithChecks(
394+
record, [&](ActiveTaskStatus parentStatus) {
395+
swift_task_updateNewChildWithParentAndGroupState(child, parentStatus,
396+
NULL);
397+
return true;
398+
});
399+
assert(added_record);
415400
return record;
416401
}
417402

@@ -421,6 +406,43 @@ swift_task_detachChildImpl(ChildTaskStatusRecord *record) {
421406
swift_task_removeStatusRecord(record);
422407
}
423408

409+
/* Called in the path of linking a child into a parent/group synchronously with
410+
* the parent task.
411+
*
412+
* When called to link a child into a parent directly, this does not hold the
413+
* parent's task status record lock. When called to link a child into a task
414+
* group, this holds the parent's task status record lock.
415+
*/
416+
SWIFT_EXPORT_FROM(swift_Concurrency)
417+
SWIFT_CC(swift)
418+
void swift_task_updateNewChildWithParentAndGroupState(
419+
AsyncTask *child, ActiveTaskStatus parentStatus, TaskGroup *group) {
420+
/*
421+
* We can take the fast path of just modifying the ActiveTaskStatus in the
422+
* child task since we know that it won't have any task status records and
423+
* cannot be accessed by anyone else since it hasn't been linked in yet.
424+
* Avoids the extra logic in `swift_task_cancel` and `swift_task_escalate`
425+
*/
426+
auto oldChildTaskStatus =
427+
child->_private().Status.load(std::memory_order_relaxed);
428+
assert(oldChildTaskStatus.getInnermostRecord() == NULL);
429+
430+
auto newChildTaskStatus = oldChildTaskStatus;
431+
432+
/* Parent task is cancelled or group the child task is part of (if any) is
433+
* cancelled */
434+
if (parentStatus.isCancelled() || (group && group->isCancelled())) {
435+
newChildTaskStatus = newChildTaskStatus.withCancelled();
436+
}
437+
438+
/* Parent task got escalated, make sure to propagate it to child. */
439+
if (parentStatus.isStoredPriorityEscalated()) {
440+
newChildTaskStatus = newChildTaskStatus.withEscalatedPriority(
441+
parentStatus.getStoredPriority());
442+
}
443+
child->_private().Status.store(newChildTaskStatus, std::memory_order_relaxed);
444+
}
445+
424446
SWIFT_CC(swift)
425447
static void swift_taskGroup_attachChildImpl(TaskGroup *group,
426448
AsyncTask *child) {
@@ -430,12 +452,25 @@ static void swift_taskGroup_attachChildImpl(TaskGroup *group,
430452
// Acquire the status record lock of parent - we want to synchronize with
431453
// concurrent cancellation or escalation as we're adding new tasks to the
432454
// group.
433-
434455
auto parent = swift_task_getCurrent();
435456
withStatusRecordLock(parent, LockContext::OnTask,
436-
[&](ActiveTaskStatus &status) {
437-
group->addChildTask(child);
438-
});
457+
[&](ActiveTaskStatus &parentStatus) {
458+
group->addChildTask(child);
459+
/*
460+
* After getting parent's status record lock, do some
461+
* sanity checks to see if parent task or group has
462+
* state changes that need to be propagated to the
463+
* child.
464+
*
465+
* This is the same logic that we would do if we were
466+
* adding a child task status record - see also
467+
* asyncLet_addImpl. Since we attach a child task to a
468+
* TaskGroupRecord instead, we synchronize on the
469+
* parent's task status and then update the child.
470+
*/
471+
swift_task_updateNewChildWithParentAndGroupState(
472+
child, parentStatus, group);
473+
});
439474
}
440475

441476
/****************************** CANCELLATION ******************************/

unittests/runtime/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ if(("${SWIFT_HOST_VARIANT_SDK}" STREQUAL "${SWIFT_PRIMARY_VARIANT_SDK}") AND
140140

141141
target_include_directories(SwiftRuntimeTests BEFORE PRIVATE
142142
${SWIFT_SOURCE_DIR}/stdlib/include)
143+
target_include_directories(SwiftRuntimeTests BEFORE PRIVATE
144+
${SWIFT_SOURCE_DIR}/stdlib/public)
143145

144146
# FIXME: cross-compile for all variants.
145147
target_link_libraries(SwiftRuntimeTests

unittests/runtime/CompatibilityOverrideConcurrency.cpp

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -227,18 +227,6 @@ TEST_F(CompatibilityOverrideConcurrencyTest, test_swift_task_localsCopyTo) {
227227
swift_task_localsCopyTo(nullptr);
228228
}
229229

230-
TEST_F(CompatibilityOverrideConcurrencyTest, test_swift_task_addStatusRecord) {
231-
swift_task_addStatusRecord(nullptr);
232-
}
233-
234-
TEST_F(CompatibilityOverrideConcurrencyTest, test_swift_task_tryAddStatusRecord) {
235-
swift_task_tryAddStatusRecord(nullptr);
236-
}
237-
238-
TEST_F(CompatibilityOverrideConcurrencyTest, test_swift_task_removeStatusRecord) {
239-
swift_task_removeStatusRecord(nullptr);
240-
}
241-
242230
TEST_F(CompatibilityOverrideConcurrencyTest, task_hasTaskGroupStatusRecord) {
243231
swift_task_hasTaskGroupStatusRecord();
244232
}

0 commit comments

Comments
 (0)