Skip to content

Commit 2be52a7

Browse files
authored
[Concurrency] Cancelled group should only spawn already cancelled tasks (swiftlang#38073)
1 parent 4939e8f commit 2be52a7

File tree

6 files changed

+27
-16
lines changed

6 files changed

+27
-16
lines changed

include/swift/ABI/TaskGroup.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ class alignas(Alignment_TaskGroup) TaskGroup {
3939

4040
/// Upon a future task's completion, offer it to the task group it belongs to.
4141
void offer(AsyncTask *completed, AsyncContext *context);
42+
43+
/// Checks the cancellation status of the group.
44+
bool isCancelled();
4245
};
4346

4447
} // end namespace swift

stdlib/public/Concurrency/Task.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,8 @@ static AsyncTaskAndContext swift_task_create_group_future_commonImpl(
557557
// In a task group we would not have allowed the `add` to create a child anymore,
558558
// however better safe than sorry and `async let` are not expressed as task groups,
559559
// so they may have been spawned in any case still.
560-
if (swift_task_isCancelled(parent))
560+
if (swift_task_isCancelled(parent) ||
561+
(group && group->isCancelled()))
561562
swift_task_cancel(task);
562563

563564
// Initialize task locals with a link to the parent task.

stdlib/public/Concurrency/TaskGroup.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,10 @@ void TaskGroup::offer(AsyncTask *completedTask, AsyncContext *context) {
508508
asImpl(this)->offer(completedTask, context);
509509
}
510510

511+
bool TaskGroup::isCancelled() {
512+
return asImpl(this)->isCancelled();
513+
}
514+
511515
static void fillGroupNextResult(TaskFutureWaitAsyncContext *context,
512516
PollResult result) {
513517
/// Fill in the result value

stdlib/public/Concurrency/TaskGroup.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -658,12 +658,12 @@ public struct ThrowingTaskGroup<ChildTaskResult, Failure: Error> {
658658
_taskGroupIsEmpty(_group)
659659
}
660660

661-
/// Cancel all of the remaining tasks in the group.
662-
///
663-
/// After canceling a group, adding a new task to it always fails.
661+
/// Cancel all the remaining, and future, tasks in the group.
664662
///
665-
/// Any results, including errors thrown by tasks affected by this
666-
/// cancellation, are silently discarded.
663+
/// A cancelled group will not will create new tasks when the `asyncUnlessCancelled`,
664+
/// function is used. It will, however, continue to create tasks when the plain `async`
665+
/// function is used. Such tasks will be created yet immediately cancelled, allowing
666+
/// the tasks to perform some short-cut implementation, if they are responsive to cancellation.
667667
///
668668
/// This function may be called even from within child (or any other) tasks,
669669
/// and causes the group to be canceled.

test/Concurrency/Runtime/async_taskgroup_cancelAll_only_specific_group.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ func test_taskGroup_cancelAll_onlySpecificGroup() async {
2222
async let g1: Int = withTaskGroup(of: Int.self) { group in
2323

2424
for i in 1...5 {
25-
group.spawn {
25+
group.async {
2626
await Task.sleep(1_000_000_000)
2727
let c = Task.isCancelled
2828
print("add: \(i) (cancelled: \(c))")
@@ -31,7 +31,7 @@ func test_taskGroup_cancelAll_onlySpecificGroup() async {
3131
}
3232

3333
var sum = 0
34-
while let got = try! await group.next() {
34+
while let got = await group.next() {
3535
print("next: \(got)")
3636
sum += got
3737
}
@@ -45,9 +45,9 @@ func test_taskGroup_cancelAll_onlySpecificGroup() async {
4545
}
4646

4747
// The cancellation os g2 should have no impact on g1
48-
let g2: Int = try! await withTaskGroup(of: Int.self) { group in
48+
let g2: Int = await withTaskGroup(of: Int.self) { group in
4949
for i in 1...3 {
50-
group.spawn {
50+
group.async {
5151
await Task.sleep(1_000_000_000)
5252
let c = Task.isCancelled
5353
print("g1 task \(i) (cancelled: \(c))")
@@ -65,8 +65,8 @@ func test_taskGroup_cancelAll_onlySpecificGroup() async {
6565
return 0
6666
}
6767

68-
let result1 = try! await g1
69-
let result2 = try! await g2
68+
let result1 = await g1
69+
let result2 = g2
7070

7171
// CHECK: g2 task cancelled: false
7272
// CHECK: g2 group cancelled: true

test/Concurrency/Runtime/async_taskgroup_cancel_then_spawn.swift

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,16 @@ func test_taskGroup_cancel_then_add() async {
3737
let none = await group.next()
3838
print("next second: \(none)") // CHECK: next second: nil
3939

40-
group.spawn { 3 }
41-
print("added third, unconditionally") // CHECK: added third, unconditionally
42-
print("group isCancelled: \(group.isCancelled)") // CHECK: group isCancelled: true
43-
40+
group.spawn {
41+
print("child task isCancelled: \(Task.isCancelled)") // CHECK: child task isCancelled: true
42+
return 3
43+
}
4444
let three = await group.next()!
4545
print("next third: \(three)") // CHECK: next third: 3
4646

47+
print("added third, unconditionally") // CHECK: added third, unconditionally
48+
print("group isCancelled: \(group.isCancelled)") // CHECK: group isCancelled: true
49+
4750
return one + (none ?? 0)
4851
}
4952

0 commit comments

Comments
 (0)