Skip to content

Commit c3ae5a1

Browse files
authored
Merge pull request #38218 from ktoso/wip-tasklocal
2 parents aa3ede1 + bb16d73 commit c3ae5a1

File tree

4 files changed

+138
-8
lines changed

4 files changed

+138
-8
lines changed

include/swift/ABI/TaskLocal.h

+10-1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,15 @@ class TaskLocal {
6969
// Trailing storage for the value itself. The storage will be
7070
// uninitialized or contain an instance of \c valueType.
7171

72+
/// Returns true if this item is a 'parent pointer'.
73+
///
74+
/// A parent pointer is special kind of `Item` is created when pointing at
75+
/// the parent storage, forming a chain of task local items spanning multiple
76+
/// tasks.
77+
bool isParentPointer() const {
78+
return !valueType;
79+
}
80+
7281
protected:
7382
explicit Item()
7483
: next(0),
@@ -174,7 +183,7 @@ class TaskLocal {
174183
/// returns, as such, any child tasks potentially accessing the value stack
175184
/// are guaranteed to be completed by the time we pop values off the stack
176185
/// (after the body has completed).
177-
TaskLocal::Item *head;
186+
TaskLocal::Item *head = nullptr;
178187

179188
public:
180189

stdlib/public/Concurrency/TaskLocal.cpp

+8-4
Original file line numberDiff line numberDiff line change
@@ -164,11 +164,9 @@ void TaskLocal::Storage::initializeLinkParent(AsyncTask* task,
164164
TaskLocal::Item*
165165
TaskLocal::Item::createParentLink(AsyncTask *task, AsyncTask *parent) {
166166
size_t amountToAllocate = Item::itemSize(/*valueType*/nullptr);
167-
// assert(amountToAllocate % MaximumAlignment == 0); // TODO: do we need this?
168167
void *allocation = _swift_task_alloc_specific(task, amountToAllocate);
169168
Item *item = new(allocation) Item();
170169

171-
// FIXME: parent pointer must to be the parent STORAGE not just the next item.
172170
auto parentHead = parent->_private().Local.head;
173171
if (parentHead) {
174172
if (parentHead->isEmpty()) {
@@ -223,8 +221,14 @@ TaskLocal::Item::createLink(AsyncTask *task,
223221
void TaskLocal::Item::copyTo(AsyncTask *target) {
224222
assert(target && "TaskLocal item attempt to copy to null target task!");
225223

226-
auto item = Item::createLink(target, this->key, this->valueType);
227-
valueType->vw_initializeWithCopy(item->getStoragePtr(), this->getStoragePtr());
224+
// 'parent' pointers are signified by null valueType.
225+
// We must not copy parent pointers, but rather perform a deep copy of all values,
226+
// as such, we skip parent pointers here entirely.
227+
if (isParentPointer())
228+
return;
229+
230+
auto item = Item::createLink(target, key, valueType);
231+
valueType->vw_initializeWithCopy(item->getStoragePtr(), getStoragePtr());
228232

229233
/// A `copyTo` may ONLY be invoked BEFORE the task is actually scheduled,
230234
/// so right now we can safely copy the value into the task without additional

test/Concurrency/Runtime/async_task_locals_copy_to_async.swift

+37-3
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ class CustomClass {
9595
}
9696

9797
@available(SwiftStdlib 5.5, *)
98-
func test_async_retains() async {
98+
func test_unstructured_retains() async {
9999
let instance = CustomClass()
100100
CustomClass.$current.withValue(instance) {
101101
print("BEFORE send: \(String(reflecting: CustomClass.current))")
@@ -119,12 +119,46 @@ func test_async_retains() async {
119119
await Task.sleep(2 * 1_000_000_000)
120120
}
121121

122+
@available(SwiftStdlib 5.5, *)
123+
func test_unstructured_noValues() async {
124+
await Task {
125+
// no values to copy
126+
}.value
127+
}
128+
129+
@available(SwiftStdlib 5.5, *)
130+
func downloadImage(from url: String) async throws -> String {
131+
await Task.sleep(10_000)
132+
return ""
133+
}
134+
135+
@available(SwiftStdlib 5.5, *)
136+
func test_unstructured_noValues_childTasks() async {
137+
@Sendable func work() async throws {
138+
let handle = Task {
139+
try await downloadImage(from: "")
140+
}
141+
}
142+
143+
// these child tasks have a parent pointer in their task local storage.
144+
// we must not copy it when performing the copyTo for a new unstructured task.
145+
async let one = work()
146+
async let two = work()
147+
async let three = work()
148+
149+
try! await one
150+
try! await two
151+
try! await three
152+
153+
}
154+
122155
@available(SwiftStdlib 5.5, *)
123156
@main struct Main {
124157
static func main() async {
125-
await copyTo_async()
126158
await copyTo_async()
127159
await copyTo_async_noWait()
128-
await test_async_retains()
160+
await test_unstructured_retains()
161+
await test_unstructured_noValues()
162+
await test_unstructured_noValues_childTasks()
129163
}
130164
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// RUN: %target-run-simple-swift(-Xfrontend -enable-experimental-concurrency -parse-as-library %import-libdispatch) | %FileCheck %s
2+
3+
// REQUIRES: executable_test
4+
// REQUIRES: concurrency
5+
// REQUIRES: libdispatch
6+
7+
// rdar://76038845
8+
// UNSUPPORTED: use_os_stdlib
9+
// UNSUPPORTED: back_deployment_runtime
10+
11+
import Dispatch
12+
13+
// For sleep
14+
#if canImport(Darwin)
15+
import Darwin
16+
#elseif canImport(Glibc)
17+
import Glibc
18+
#endif
19+
20+
@available(SwiftStdlib 5.5, *)
21+
enum TL {
22+
@TaskLocal
23+
static var number: Int = 0
24+
@TaskLocal
25+
static var other: Int = 0
26+
}
27+
28+
@available(SwiftStdlib 5.5, *)
29+
@discardableResult
30+
func printTaskLocal<V>(
31+
_ key: TaskLocal<V>,
32+
_ expected: V? = nil,
33+
file: String = #file, line: UInt = #line
34+
) -> V? {
35+
let value = key.get()
36+
print("\(key) (\(value)) at \(file):\(line)")
37+
if let expected = expected {
38+
assert("\(expected)" == "\(value)",
39+
"Expected [\(expected)] but found: \(value), at \(file):\(line)")
40+
}
41+
return expected
42+
}
43+
44+
// ==== ------------------------------------------------------------------------
45+
46+
@available(SwiftStdlib 5.5, *)
47+
func copyTo_sync_noWait() {
48+
print(#function)
49+
TL.$number.withValue(1111) {
50+
TL.$number.withValue(2222) {
51+
TL.$other.withValue(9999) {
52+
Task {
53+
printTaskLocal(TL.$number) // CHECK: TaskLocal<Int>(defaultValue: 0) (2222)
54+
printTaskLocal(TL.$other) // CHECK: TaskLocal<Int>(defaultValue: 0) (9999)
55+
TL.$number.withValue(3333) {
56+
printTaskLocal(TL.$number) // CHECK: TaskLocal<Int>(defaultValue: 0) (3333)
57+
printTaskLocal(TL.$other) // CHECK: TaskLocal<Int>(defaultValue: 0) (9999)
58+
}
59+
}
60+
}
61+
}
62+
}
63+
64+
sleep(1)
65+
}
66+
67+
@available(SwiftStdlib 5.5, *)
68+
func copyTo_sync_noValues() {
69+
Task {
70+
printTaskLocal(TL.$number) // CHECK: TaskLocal<Int>(defaultValue: 0) (0)
71+
}
72+
73+
sleep(1)
74+
}
75+
76+
/// Similar to tests in `async_task_locals_copy_to_async_ but without any task involved at the top level.
77+
@available(SwiftStdlib 5.5, *)
78+
@main struct Main {
79+
static func main() {
80+
copyTo_sync_noWait()
81+
copyTo_sync_noValues()
82+
}
83+
}

0 commit comments

Comments
 (0)