Skip to content

Commit b99b37a

Browse files
committed
[Concurrency] Implement only-when-necessary defensive TL copying in in task groups
1 parent 478ba49 commit b99b37a

10 files changed

+262
-122
lines changed

include/swift/ABI/TaskLocal.h

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,25 @@ class TaskLocal {
125125
return reinterpret_cast<Item *>(next & ~statusMask);
126126
}
127127

128+
void relinkNext(Item* nextOverride) {
129+
fprintf(stderr, "[%s:%d](%s) this item = %p\n", __FILE_NAME__, __LINE__, __FUNCTION__, this);
130+
fprintf(stderr, "[%s:%d](%s) NEXT = %p\n", __FILE_NAME__, __LINE__, __FUNCTION__, getNext());
131+
fprintf(stderr, "[%s:%d](%s) storage = %p\n", __FILE_NAME__, __LINE__, __FUNCTION__, getStoragePtr());
132+
fprintf(stderr, "[%s:%d](%s) next kind = %d\n", __FILE_NAME__, __LINE__, __FUNCTION__, getNextLinkType());
133+
assert(!getNext() &&
134+
"Can only relink task local item that was not pointing at anything yet");
135+
assert(nextOverride->isNextLinkPointer() ||
136+
nextOverride->isParentPointer() &&
137+
"Currently relinking is only done within a task group to "
138+
"avoid within-taskgroup next pointers; attempted to point at "
139+
"task local declared within task group body though!");
140+
141+
next = reinterpret_cast<uintptr_t>(nextOverride) |
142+
static_cast<uintptr_t>((nextOverride->isNextLinkPointer()
143+
? NextLinkType::IsNextCreatedInTaskGroupBody
144+
: NextLinkType::IsParent));
145+
}
146+
128147
NextLinkType getNextLinkType() const {
129148
return static_cast<NextLinkType>(next & statusMask);
130149
}
@@ -134,7 +153,7 @@ class TaskLocal {
134153
NextLinkType::IsNext;
135154
}
136155

137-
bool IsNextCreatedInTaskGroupBody() const {
156+
bool isNextCreatedInTaskGroupBody() const {
138157
return static_cast<NextLinkType>(next & statusMask) ==
139158
NextLinkType::IsNextCreatedInTaskGroupBody;
140159
}
@@ -151,7 +170,7 @@ class TaskLocal {
151170
reinterpret_cast<char *>(this) + storageOffset(valueType));
152171
}
153172

154-
void copyTo(AsyncTask *task);
173+
TaskLocal::Item* copyTo(AsyncTask *task);
155174

156175
/// Compute the offset of the storage from the base of the item.
157176
static size_t storageOffset(const Metadata *valueType) {
@@ -238,6 +257,8 @@ class TaskLocal {
238257
/// "pop" of the `B` value - it was spawned from a scope where only B was observable.
239258
void copyTo(AsyncTask *target);
240259

260+
void copyToOnlyOnlyFromCurrent(AsyncTask *target);
261+
241262
/// Destroy and deallocate all items stored by this specific task.
242263
///
243264
/// Items owned by a parent task are left untouched, since we do not own them.

include/swift/Runtime/Concurrency.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -663,11 +663,15 @@ void swift_task_localValuePop();
663663
/// Its Swift signature is
664664
///
665665
/// \code
666-
/// func _taskLocalValueGet<Key>(AsyncTask* task)
666+
/// func swift_task_localsCopyTo<Key>(AsyncTask* task)
667667
/// \endcode
668668
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
669669
void swift_task_localsCopyTo(AsyncTask* target);
670670

671+
// FIXME no need for runtime API
672+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
673+
void swift_task_localsOnlyCurrentCopyTo(AsyncTask* target);
674+
671675
/// Switch the current task to a new executor if we aren't already
672676
/// running on a compatible executor.
673677
///

stdlib/public/CompatibilityOverride/CompatibilityOverrideConcurrency.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,12 @@ OVERRIDE_TASK_LOCAL(task_localsCopyTo, void,
378378
(AsyncTask *target),
379379
(target))
380380

381+
OVERRIDE_TASK_LOCAL(task_localsOnlyCurrentCopyTo, void,
382+
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift),
383+
swift::,
384+
(AsyncTask *target),
385+
(target))
386+
381387
OVERRIDE_TASK_STATUS(task_hasTaskGroupStatusRecord, bool,
382388
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift),
383389
swift::, , )

stdlib/public/Concurrency/Task.cpp

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -732,24 +732,25 @@ swift_task_create_commonImpl(size_t rawTaskCreateFlags,
732732
AsyncTask *currentTask = swift_task_getCurrent();
733733
AsyncTask *parent = jobFlags.task_isChildTask() ? currentTask : nullptr;
734734

735+
/// FIXME: this is the fail path
735736
if (group) {
736737
assert(parent && "a task created in a group must be a child task");
737-
738-
// Prevent task-local misuse;
739-
// We must not allow an addTask {} wrapped immediately with a withValue {}
740-
auto ParentLocal = parent->_private().Local;
741-
if (auto taskLocalHeadLinkType = ParentLocal.peekHeadLinkType()) {
742-
if (taskLocalHeadLinkType ==
743-
swift::TaskLocal::NextLinkType::IsNextCreatedInTaskGroupBody) {
744-
#if !SWIFT_CONCURRENCY_EMBEDDED
745-
swift_task_reportIllegalTaskLocalBindingWithinWithTaskGroup(
746-
nullptr, 0, true, 0);
747-
#endif
748-
// TODO(ktoso): If we were to keep this crash mode; offer a better failure for embedded swift
749-
abort();
750-
}
751-
}
752-
738+
//
739+
// // Prevent task-local misuse;
740+
// // We must not allow an addTask {} wrapped immediately with a withValue {}
741+
// auto ParentLocal = parent->_private().Local;
742+
// if (auto taskLocalHeadLinkType = ParentLocal.peekHeadLinkType()) {
743+
// if (taskLocalHeadLinkType ==
744+
// swift::TaskLocal::NextLinkType::IsNextCreatedInTaskGroupBody) {
745+
//#if !SWIFT_CONCURRENCY_EMBEDDED
746+
// swift_task_reportIllegalTaskLocalBindingWithinWithTaskGroup(
747+
// nullptr, 0, true, 0);
748+
//#endif
749+
// // TODO(ktoso): If we were to keep this crash mode; offer a better failure for embedded swift
750+
// abort();
751+
// }
752+
// }
753+
//
753754
// Add to the task group, if requested.
754755
if (taskCreateFlags.addPendingGroupTaskUnconditionally()) {
755756
assert(group && "Missing group");
@@ -998,12 +999,44 @@ swift_task_create_commonImpl(size_t rawTaskCreateFlags,
998999
// In a task group we would not have allowed the `add` to create a child anymore,
9991000
// however better safe than sorry and `async let` are not expressed as task groups,
10001001
// so they may have been spawned in any case still.
1001-
if (swift_task_isCancelled(parent) ||
1002-
(group && group->isCancelled()))
1002+
if ((group && group->isCancelled()) || swift_task_isCancelled(parent))
10031003
swift_task_cancel(task);
10041004

1005-
// Initialize task locals with a link to the parent task.
1006-
task->_private().Local.initializeLinkParent(task, parent);
1005+
// Initialize task locals storage
1006+
bool taskLocalStorageInitialized = false;
1007+
1008+
// Inside a task group, we may have to perform some defensive copying,
1009+
// check if doing so is necessary, and initialize storage using partial
1010+
// defensive copies if necessary.
1011+
if (group) {
1012+
assert(parent && "a task created in a group must be a child task");
1013+
// We are a child task in a task group; and it may happen that we are calling
1014+
// addTask specifically in such shape:
1015+
//
1016+
// $local.withValue(theValue) { addTask {} }
1017+
//
1018+
// If this is the case, we MUST copy `theValue` (and any other such directly
1019+
// wrapping the addTask value bindings), because those values will be popped
1020+
// when withValue returns - breaking our structured concurrency guarantees
1021+
// that we rely on for the "link directly to parent's task local Item".
1022+
//
1023+
// Values set outside the task group are not subject to this problem, as
1024+
// their structural lifetime guarantee is upheld by the group scope
1025+
// out-living any addTask created tasks.
1026+
auto ParentLocal = parent->_private().Local;
1027+
if (auto taskLocalHeadLinkType = ParentLocal.peekHeadLinkType()) {
1028+
if (taskLocalHeadLinkType ==
1029+
swift::TaskLocal::NextLinkType::IsNextCreatedInTaskGroupBody) {
1030+
swift_task_localsOnlyCurrentCopyTo(task);
1031+
taskLocalStorageInitialized = true;
1032+
}
1033+
}
1034+
}
1035+
1036+
if (!taskLocalStorageInitialized) {
1037+
// just initialize the storage normally
1038+
task->_private().Local.initializeLinkParent(task, parent);
1039+
}
10071040
}
10081041

10091042
// Configure the initial context.
@@ -1065,7 +1098,7 @@ swift_task_create_commonImpl(size_t rawTaskCreateFlags,
10651098
#endif
10661099
swift_retain(task);
10671100
task->flagAsAndEnqueueOnExecutor(
1068-
serialExecutor); // FIXME: pass the task executor explicitly?
1101+
serialExecutor);
10691102
}
10701103

10711104
return {task, initialContext};

0 commit comments

Comments
 (0)