Skip to content

Commit aaec5aa

Browse files
authored
Merge pull request #80794 from ktoso/wip-parent-cancel-must-cancel-group
[Concurrency] Parent task cancellation must cancel task group itself.
2 parents cebc4d7 + a9bcf39 commit aaec5aa

File tree

5 files changed

+85
-14
lines changed

5 files changed

+85
-14
lines changed

include/swift/ABI/TaskGroup.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ class alignas(Alignment_TaskGroup) TaskGroup {
4444
/// Checks the cancellation status of the group.
4545
bool isCancelled();
4646

47+
/// Only mark the task group as cancelled, without performing the follow-up
48+
/// work of cancelling all the child tasks.
49+
///
50+
/// Returns true if the group was already cancelled before this call.
51+
bool statusCancel();
52+
4753
// Add a child task to the task group. Always called while holding the
4854
// status record lock of the task group's owning task.
4955
void addChildTask(AsyncTask *task);

stdlib/public/Concurrency/TaskGroup.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1166,6 +1166,10 @@ bool TaskGroup::isCancelled() {
11661166
return asBaseImpl(this)->isCancelled();
11671167
}
11681168

1169+
bool TaskGroup::statusCancel() {
1170+
return asBaseImpl(this)->statusCancel();
1171+
}
1172+
11691173
// =============================================================================
11701174
// ==== offer ------------------------------------------------------------------
11711175

@@ -2116,8 +2120,8 @@ bool TaskGroupBase::cancelAll(AsyncTask *owningTask) {
21162120

21172121
// Cancel all the child tasks. TaskGroup is not a Sendable type,
21182122
// so cancelAll() can only be called from the owning task. This
2119-
// satisfies the precondition on cancelAllChildren_unlocked().
2120-
_swift_taskGroup_cancelAllChildren_unlocked(asAbstract(this), owningTask);
2123+
// satisfies the precondition on cancel_unlocked().
2124+
_swift_taskGroup_cancel_unlocked(asAbstract(this), owningTask);
21212125

21222126
return true;
21232127
}
@@ -2126,8 +2130,8 @@ SWIFT_CC(swift)
21262130
static void swift_task_cancel_group_child_tasksImpl(TaskGroup *group) {
21272131
// TaskGroup is not a Sendable type, and so this operation (which is not
21282132
// currently exposed in the API) can only be called from the owning
2129-
// task. This satisfies the precondition on cancelAllChildren_unlocked().
2130-
_swift_taskGroup_cancelAllChildren_unlocked(group, swift_task_getCurrent());
2133+
// task. This satisfies the precondition on cancel_unlocked().
2134+
_swift_taskGroup_cancel_unlocked(group, swift_task_getCurrent());
21312135
}
21322136

21332137
// =============================================================================

stdlib/public/Concurrency/TaskPrivate.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,16 +89,16 @@ AsyncTask *_swift_task_clearCurrent();
8989
/// Set the active task reference for the current thread.
9090
AsyncTask *_swift_task_setCurrent(AsyncTask *newTask);
9191

92-
/// Cancel all the child tasks that belong to `group`.
92+
/// Cancel the task group and all the child tasks that belong to `group`.
9393
///
9494
/// The caller must guarantee that this is called while holding the owning
9595
/// task's status record lock.
96-
void _swift_taskGroup_cancelAllChildren(TaskGroup *group);
96+
void _swift_taskGroup_cancel(TaskGroup *group);
9797

98-
/// Cancel all the child tasks that belong to `group`.
98+
/// Cancel the task group and all the child tasks that belong to `group`.
9999
///
100100
/// The caller must guarantee that this is called from the owning task.
101-
void _swift_taskGroup_cancelAllChildren_unlocked(TaskGroup *group,
101+
void _swift_taskGroup_cancel_unlocked(TaskGroup *group,
102102
AsyncTask *owningTask);
103103

104104
/// Remove the given task from the given task group.

stdlib/public/Concurrency/TaskStatus.cpp

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -776,11 +776,13 @@ void swift::_swift_taskGroup_detachChild(TaskGroup *group,
776776
});
777777
}
778778

779-
/// Cancel all the child tasks that belong to `group`.
779+
/// Cancel the task group and all the child tasks that belong to `group`.
780780
///
781781
/// The caller must guarantee that this is called while holding the owning
782782
/// task's status record lock.
783-
void swift::_swift_taskGroup_cancelAllChildren(TaskGroup *group) {
783+
void swift::_swift_taskGroup_cancel(TaskGroup *group) {
784+
(void) group->statusCancel();
785+
784786
// Because only the owning task of the task group can modify the
785787
// child list of a task group status record, and it can only do so
786788
// while holding the owning task's status record lock, we do not need
@@ -789,10 +791,10 @@ void swift::_swift_taskGroup_cancelAllChildren(TaskGroup *group) {
789791
swift_task_cancel(childTask);
790792
}
791793

792-
/// Cancel all the child tasks that belong to `group`.
794+
/// Cancel the task group and all the child tasks that belong to `group`.
793795
///
794796
/// The caller must guarantee that this is called from the owning task.
795-
void swift::_swift_taskGroup_cancelAllChildren_unlocked(TaskGroup *group,
797+
void swift::_swift_taskGroup_cancel_unlocked(TaskGroup *group,
796798
AsyncTask *owningTask) {
797799
// Early out. If there are no children, there's nothing to do. We can safely
798800
// check this without locking, since this can only be concurrently mutated
@@ -801,7 +803,7 @@ void swift::_swift_taskGroup_cancelAllChildren_unlocked(TaskGroup *group,
801803
return;
802804

803805
withStatusRecordLock(owningTask, [&group](ActiveTaskStatus status) {
804-
_swift_taskGroup_cancelAllChildren(group);
806+
_swift_taskGroup_cancel(group);
805807
});
806808
}
807809

@@ -825,7 +827,7 @@ static void performCancellationAction(TaskStatusRecord *record) {
825827
// under the synchronous control of the task that owns the group.
826828
case TaskStatusRecordKind::TaskGroup: {
827829
auto groupRecord = cast<TaskGroupTaskStatusRecord>(record);
828-
_swift_taskGroup_cancelAllChildren(groupRecord->getGroup());
830+
_swift_taskGroup_cancel(groupRecord->getGroup());
829831
return;
830832
}
831833

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// RUN: %target-run-simple-swift( -target %target-swift-5.1-abi-triple -parse-as-library) | %FileCheck %s
2+
3+
// REQUIRES: executable_test
4+
// REQUIRES: concurrency
5+
6+
// REQUIRES: concurrency_runtime
7+
// UNSUPPORTED: back_deployment_runtime
8+
9+
@available(SwiftStdlib 6.0, *)
10+
func test_withTaskGroup_addUnlessCancelled() async throws {
11+
let task = Task {
12+
await withTaskGroup(of: Void.self) { group in
13+
print("Inner: Sleep...")
14+
try? await Task.sleep(for: .seconds(60)) // we'll never actually wait 10 seconds, as this will be woken up by cancel
15+
print("Inner: Task.isCancelled: \(Task.isCancelled)")
16+
17+
let added = group.addTaskUnlessCancelled {
18+
print("Added Task! Child Task.isCancelled: \(Task.isCancelled)")
19+
}
20+
print("Inner: Task added = \(added)") // CHECK: Inner: Task added = false
21+
}
22+
}
23+
24+
print("Outer: Cancel!")
25+
task.cancel()
26+
print("Outer: Cancelled")
27+
28+
await task.value
29+
}
30+
31+
@available(SwiftStdlib 6.0, *)
32+
func test_withDiscardingTaskGroup_addUnlessCancelled() async throws {
33+
let task = Task {
34+
await withDiscardingTaskGroup { group in
35+
print("Inner: Sleep...")
36+
try? await Task.sleep(for: .seconds(60)) // we'll never actually wait 10 seconds, as this will be woken up by cancel
37+
print("Inner: Task.isCancelled: \(Task.isCancelled)")
38+
39+
let added = group.addTaskUnlessCancelled {
40+
print("Added Task! Child Task.isCancelled: \(Task.isCancelled)")
41+
}
42+
print("Inner: Task added = \(added)") // CHECK: Inner: Task added = false
43+
}
44+
}
45+
46+
print("Outer: Cancel!")
47+
task.cancel()
48+
print("Outer: Cancelled")
49+
50+
await task.value
51+
}
52+
53+
@available(SwiftStdlib 6.0, *)
54+
@main struct Main {
55+
static func main() async {
56+
try! await test_withTaskGroup_addUnlessCancelled()
57+
try! await test_withDiscardingTaskGroup_addUnlessCancelled()
58+
}
59+
}

0 commit comments

Comments
 (0)