Skip to content

Commit 72f14b5

Browse files
committed
[Discarding] Don't leak retained "first error" task when retaining it
1 parent 5ca9af6 commit 72f14b5

File tree

2 files changed

+132
-6
lines changed

2 files changed

+132
-6
lines changed

stdlib/public/Concurrency/TaskGroup.cpp

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ class TaskGroupBase : public TaskGroupTaskStatusRecord {
228228
reinterpret_cast<OpaqueValue *>(fragment->getError()) :
229229
fragment->getStoragePtr(),
230230
/*successType=*/fragment->getResultType(),
231-
/*task=*/asyncTask
231+
/*retainedTask==*/asyncTask
232232
};
233233
}
234234

@@ -1040,7 +1040,7 @@ static void fillGroupNextResult(TaskFutureWaitAsyncContext *context,
10401040

10411041
case PollStatus::Error: {
10421042
auto error = reinterpret_cast<SwiftError *>(result.storage);
1043-
fillGroupNextErrorResult(context, error);
1043+
fillGroupNextErrorResult(context, error); // FIXME: this specifically retains the error, but likely should not!??!!?
10441044
return;
10451045
}
10461046

@@ -1218,7 +1218,6 @@ void DiscardingTaskGroup::offer(AsyncTask *completedTask, AsyncContext *context)
12181218
// We can do this, since in this mode there is no ready count to keep track of,
12191219
// and we immediately discard the result.
12201220
auto afterComplete = statusCompletePendingAssumeRelease();
1221-
(void) afterComplete;
12221221
const bool alreadyDecrementedStatus = true;
12231222
SWIFT_TASK_GROUP_DEBUG_LOG(this, "offer, complete, status afterComplete:%s", afterComplete.to_string(this).c_str());
12241223

@@ -1351,8 +1350,8 @@ void TaskGroupBase::resumeWaitingTask(
13511350
// Run the task.
13521351
auto result = PollResult::get(completedTask, hadErrorResult);
13531352
SWIFT_TASK_GROUP_DEBUG_LOG(this,
1354-
"resume waiting DONE, task = %p, backup = %p, error:%d, complete with = %p, status = %s",
1355-
waitingTask, backup, hadErrorResult, completedTask, statusString().c_str());
1353+
"resume waiting DONE, task = %p, error:%d, complete with = %p, status = %s",
1354+
waitingTask, hadErrorResult, completedTask, statusString().c_str());
13561355

13571356
auto waitingContext =
13581357
static_cast<TaskFutureWaitAsyncContext *>(
@@ -1369,6 +1368,11 @@ void TaskGroupBase::resumeWaitingTask(
13691368
// locks) because we know that the child task is completed and
13701369
// we can't be holding its locks ourselves.
13711370
_swift_taskGroup_detachChild(asAbstract(this), completedTask);
1371+
if (hadErrorResult) {
1372+
// We only used the task to keep the error in the future fragment around
1373+
// so now that we emitted the error and detached the task, we are free to release the task immediately.
1374+
swift_release(completedTask);
1375+
}
13721376

13731377
_swift_tsan_acquire(static_cast<Job *>(waitingTask));
13741378
// TODO: allow the caller to suggest an executor
@@ -1377,7 +1381,7 @@ void TaskGroupBase::resumeWaitingTask(
13771381
#endif /* SWIFT_CONCURRENCY_TASK_TO_THREAD_MODEL */
13781382
} else {
13791383
SWIFT_TASK_GROUP_DEBUG_LOG(this, "CAS failed, task = %p, backup = %p, complete with = %p, status = %s",
1380-
waitingTask, backup, completedTask, statusString().c_str());
1384+
waitingTask, completedTask, statusString().c_str());
13811385
}
13821386
}
13831387
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// RUN: %target-run-simple-leaks-swift( -Xfrontend -disable-availability-checking -parse-as-library)
2+
3+
// This test uses `leaks` which is only available on apple platforms; limit it to macOS:
4+
// REQUIRES: OS=macosx
5+
6+
// REQUIRES: concurrency
7+
// REQUIRES: executable_test
8+
// REQUIRES: concurrency_runtime
9+
10+
// UNSUPPORTED: back_deployment_runtime
11+
// UNSUPPORTED: freestanding
12+
13+
// FIXME: enable discarding taskgroup on windows; rdar://104762037
14+
// UNSUPPORTED: OS=windows-msvc
15+
16+
import _Concurrency
17+
18+
struct Boom: Error {
19+
let id: String
20+
21+
init(file: String = #fileID, line: UInt = #line) {
22+
self.id = "\(file):\(line)"
23+
}
24+
25+
init(id: String) {
26+
self.id = id
27+
}
28+
}
29+
30+
struct IgnoredBoom: Error {}
31+
32+
@discardableResult
33+
func echo(_ i: Int) -> Int { i }
34+
@discardableResult
35+
func boom(file: String = #fileID, line: UInt = #line) throws -> Int { throw Boom(file: file, line: line) }
36+
37+
func shouldEqual<T: Equatable>(_ lhs: T, _ rhs: T) {
38+
precondition(lhs == rhs, "'\(lhs)' did not equal '\(rhs)'")
39+
}
40+
func shouldStartWith(_ lhs: Any, _ rhs: Any) {
41+
let l = "\(lhs)"
42+
let r = "\(rhs)"
43+
precondition(l.prefix("\(r)".count) == r, "'\(l)' did not start with '\(r)'")
44+
}
45+
46+
final class SomeClass: @unchecked Sendable {
47+
init() {}
48+
}
49+
50+
// NOTE: Not as StdlibUnittest/TestSuite since these types of tests are unreasonably slow to load/debug.
51+
52+
@main struct Main {
53+
static func main() async {
54+
// one error, one ok
55+
// _ = try? await withThrowingDiscardingTaskGroup() { group in
56+
// group.addTask {
57+
// try boom()
58+
// }
59+
// group.addTask {
60+
// return SomeClass() // will be discarded
61+
// }
62+
//
63+
// return 12
64+
// }
65+
//
66+
// // many ok
67+
// _ = try? await withThrowingDiscardingTaskGroup() { group in
68+
// for i in 0..<100 {
69+
// group.addTask {
70+
// return SomeClass() // will be discarded
71+
// }
72+
// }
73+
//
74+
// return 12
75+
// }
76+
77+
// many errors
78+
_ = try? await withThrowingDiscardingTaskGroup() { group in
79+
group.addTask {
80+
try boom()
81+
}
82+
group.addTask {
83+
try boom()
84+
}
85+
group.addTask {
86+
try boom()
87+
}
88+
group.addTask {
89+
try boom()
90+
}
91+
92+
return 12
93+
}
94+
//
95+
// // many errors, many values
96+
// _ = try? await withThrowingDiscardingTaskGroup() { group in
97+
// group.addTask {
98+
// return SomeClass() // will be discarded
99+
// }
100+
// group.addTask {
101+
// return SomeClass() // will be discarded
102+
// }
103+
// group.addTask {
104+
// return SomeClass() // will be discarded
105+
// }
106+
// group.addTask {
107+
// try boom()
108+
// }
109+
// group.addTask {
110+
// try boom()
111+
// }
112+
// group.addTask {
113+
// try boom()
114+
// }
115+
// group.addTask {
116+
// try boom()
117+
// }
118+
//
119+
// return 12
120+
// }
121+
}
122+
}

0 commit comments

Comments
 (0)