Skip to content

Commit 67ab0d7

Browse files
Implement disabling task-local values in isolated deinit
As requested by the LSG during the proposal review. See https://forums.swift.org/t/accepted-with-modifications-se-0371-isolated-synchronous-deinit/74042
1 parent 7ea2b3d commit 67ab0d7

File tree

4 files changed

+91
-16
lines changed

4 files changed

+91
-16
lines changed

stdlib/public/Concurrency/Actor.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2350,7 +2350,8 @@ static void swift_task_deinitOnExecutorImpl(void *object,
23502350
// when running on the main thread without any executor.
23512351
if (swift_task_isCurrentExecutorWithFlags(
23522352
newExecutor, swift_task_is_current_executor_flag::None)) {
2353-
return work(object); // 'return' forces tail call
2353+
TaskLocal::StopLookupScope scope;
2354+
return work(object);
23542355
}
23552356

23562357
#if SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
@@ -2384,7 +2385,10 @@ static void swift_task_deinitOnExecutorImpl(void *object,
23842385
trackingInfo.enterAndShadow(newExecutor, taskExecutor);
23852386

23862387
// Run the work.
2387-
work(object);
2388+
{
2389+
TaskLocal::StopLookupScope scope;
2390+
work(object);
2391+
}
23882392

23892393
// `work` is a synchronous function, it cannot call swift_task_switch()
23902394
// If it calls any synchronous API that may change executor inside

stdlib/public/Concurrency/TaskLocal.cpp

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ void TaskLocal::Storage::initializeLinkParent(AsyncTask* task,
181181
if (!item)
182182
return;
183183

184-
auto tail = TaskLocal::ParentTaskMarkerItem::create(task);
184+
auto tail = TaskLocal::MarkerItem::createParentTaskMarker(task);
185185
head = tail;
186186

187187
// Set of keys for which we already have copied to the new task.
@@ -234,18 +234,24 @@ void TaskLocal::Storage::initializeLinkParent(AsyncTask* task,
234234
item = item->getNext();
235235
}
236236

237+
if (item && item->getKind() == Item::Kind::StopLookupMarker) {
238+
// Stop marker also could have been created inside task group body
239+
// But we don't need to copy it. Instead we can break the chain.
240+
item = nullptr;
241+
}
242+
237243
// The next item is not the "risky one" so we can directly link to it,
238244
// as we would have within normal child task relationships. E.g. this is
239245
// a parent or next pointer to a "safe" (withValue { withTaskGroup { ... } })
240246
// binding, so we re-link our current head to point at this item.
241247
tail->setNext(item);
242248
}
243249

244-
TaskLocal::ParentTaskMarkerItem *
245-
TaskLocal::ParentTaskMarkerItem::create(AsyncTask *task) {
246-
size_t amountToAllocate = sizeof(ParentTaskMarkerItem);
250+
TaskLocal::MarkerItem *TaskLocal::MarkerItem::create(AsyncTask *task,
251+
Item *next, Kind kind) {
252+
size_t amountToAllocate = sizeof(MarkerItem);
247253
void *allocation = _swift_task_alloc_specific(task, amountToAllocate);
248-
return new (allocation) ParentTaskMarkerItem(nullptr);
254+
return new (allocation) MarkerItem(next, kind);
249255
}
250256

251257
TaskLocal::ValueItem *TaskLocal::ValueItem::create(AsyncTask *task,
@@ -367,11 +373,12 @@ bool TaskLocal::Item::destroy(AsyncTask *task) {
367373
cast<ValueItem>(this)->~ValueItem();
368374
break;
369375
case Kind::ParentTaskMarker:
370-
cast<ParentTaskMarkerItem>(this)->~ParentTaskMarkerItem();
371-
372376
// we're done here; as we must not proceed into the parent owned values.
373377
// we do have to destroy the item pointing at the parent/edge itself though.
374378
stop = true;
379+
LLVM_FALLTHROUGH;
380+
case Kind::StopLookupMarker:
381+
cast<MarkerItem>(this)->~MarkerItem();
375382
break;
376383
}
377384

@@ -442,6 +449,21 @@ bool TaskLocal::Storage::popValue(AsyncTask *task) {
442449
return head != nullptr;
443450
}
444451

452+
void TaskLocal::Storage::pushStopLookup(AsyncTask *task) {
453+
head = MarkerItem::createStopLookupMarker(task, head);
454+
SWIFT_TASK_LOCAL_DEBUG_LOG(nullptr, "push stop node item:%p", head);
455+
}
456+
457+
void TaskLocal::Storage::popStopLookup(AsyncTask *task) {
458+
assert(head && "attempted to pop stop node off empty task-local stack");
459+
assert(head->getKind() == Item::Kind::StopLookupMarker &&
460+
"attempted to pop wrong node type");
461+
auto old = head;
462+
SWIFT_TASK_LOCAL_DEBUG_LOG(nullptr, "pop stop node item:%p", old);
463+
head = head->getNext();
464+
old->destroy(task);
465+
}
466+
445467
OpaqueValue* TaskLocal::Storage::getValue(AsyncTask *task,
446468
const HeapObject *key) {
447469
assert(key && "TaskLocal key must not be null.");
@@ -452,6 +474,8 @@ OpaqueValue* TaskLocal::Storage::getValue(AsyncTask *task,
452474
if (valueItem->key == key) {
453475
return valueItem->getStoragePtr();
454476
}
477+
} else if (item->getKind() == Item::Kind::StopLookupMarker) {
478+
break;
455479
}
456480

457481
item = item->getNext();
@@ -487,10 +511,30 @@ void TaskLocal::Storage::copyTo(AsyncTask *target) {
487511
"skip copy, already copied most recent value, value was [%p]",
488512
valueItem->getStoragePtr());
489513
}
514+
} else if (item->getKind() == Item::Kind::StopLookupMarker) {
515+
break;
490516
}
491517
item = item->getNext();
492518
}
493519
}
494520

521+
TaskLocal::StopLookupScope::StopLookupScope() {
522+
task = swift_task_getCurrent();
523+
storage = Storage::getCurrent(task);
524+
if (storage && storage->isEmpty()) {
525+
storage = nullptr;
526+
}
527+
528+
if (storage) {
529+
storage->pushStopLookup(task);
530+
}
531+
}
532+
533+
TaskLocal::StopLookupScope::~StopLookupScope() {
534+
if (storage) {
535+
storage->popStopLookup(task);
536+
}
537+
}
538+
495539
#define OVERRIDE_TASK_LOCAL COMPATIBILITY_OVERRIDE
496540
#include "../CompatibilityOverride/CompatibilityOverrideIncludePath.h"

stdlib/public/Concurrency/TaskLocal.h

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,12 @@ class TaskLocal {
5959
/// does not "contribute" any task local values. This is to speed up
6060
/// lookups by skipping empty parent tasks during get(), and explained
6161
/// in depth in `initializeLinkParent()`.
62-
ParentTaskMarker = 2
62+
ParentTaskMarker = 2,
63+
64+
/// Marker item that indicates that all earlier items should be ignored.
65+
/// Allows to temporary disable all task local values in O(1).
66+
/// Is used to disable task-locals for fast path of isolated deinit.
67+
StopLookupMarker = 3,
6368
};
6469

6570
private:
@@ -131,14 +136,22 @@ class TaskLocal {
131136
}
132137
};
133138

134-
class ParentTaskMarkerItem : public Item {
135-
ParentTaskMarkerItem(Item *next) : Item(next, Kind::ParentTaskMarker) {}
139+
class MarkerItem : public Item {
140+
MarkerItem(Item *next, Kind kind) : Item(next, kind) {}
141+
142+
static MarkerItem *create(AsyncTask *task, Item *next, Kind kind);
136143

137144
public:
138-
static ParentTaskMarkerItem *create(AsyncTask *task);
145+
static MarkerItem *createParentTaskMarker(AsyncTask *task) {
146+
return create(task, nullptr, Kind::ParentTaskMarker);
147+
}
148+
static MarkerItem *createStopLookupMarker(AsyncTask *task, Item *next) {
149+
return create(task, next, Kind::StopLookupMarker);
150+
}
139151

140152
static bool classof(const Item *item) {
141-
return item->getKind() == Kind::ParentTaskMarker;
153+
return item->getKind() == Kind::ParentTaskMarker ||
154+
item->getKind() == Kind::StopLookupMarker;
142155
}
143156
};
144157

@@ -185,6 +198,8 @@ class TaskLocal {
185198

186199
void initializeLinkParent(AsyncTask *task, AsyncTask *parent);
187200

201+
bool isEmpty() const { return head == nullptr; }
202+
188203
void pushValue(AsyncTask *task,
189204
const HeapObject *key,
190205
/* +1 */ OpaqueValue *value, const Metadata *valueType);
@@ -196,6 +211,9 @@ class TaskLocal {
196211
/// can be safely disposed of.
197212
bool popValue(AsyncTask *task);
198213

214+
void pushStopLookup(AsyncTask *task);
215+
void popStopLookup(AsyncTask *task);
216+
199217
/// Copy all task-local bindings to the target task.
200218
///
201219
/// The new bindings allocate their own items and can out-live the current task.
@@ -213,6 +231,15 @@ class TaskLocal {
213231
/// Items owned by a parent task are left untouched, since we do not own them.
214232
void destroy(AsyncTask *task);
215233
};
234+
235+
class StopLookupScope {
236+
AsyncTask *task;
237+
Storage *storage;
238+
239+
public:
240+
StopLookupScope();
241+
~StopLookupScope();
242+
};
216243
};
217244

218245
} // end namespace swift

test/Concurrency/Runtime/async_task_locals_isolated_deinit.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ if #available(SwiftStdlib 5.1, *) {
142142
// FIXME: isolated deinit should be clearing task locals
143143
await TL.$number.withValue(42) {
144144
await AnotherActor.shared.performTesting {
145-
preventAllocationOnStack(ClassNoOp(expectedNumber: 42, group: group))
145+
preventAllocationOnStack(ClassNoOp(expectedNumber: 0, group: group))
146146
}
147147
}
148148
}
@@ -168,7 +168,7 @@ if #available(SwiftStdlib 5.1, *) {
168168
TL.$number.withValue(99) {
169169
// Despite last release happening not on the actor itself,
170170
// this is still a fast path due to optimisation for deallocating actors.
171-
preventAllocationOnStack(ActorNoOp(expectedNumber: 99, group: group))
171+
preventAllocationOnStack(ActorNoOp(expectedNumber: 0, group: group))
172172
}
173173
}
174174
group.wait()

0 commit comments

Comments
 (0)