diff --git a/include/swift/ABI/TaskGroup.h b/include/swift/ABI/TaskGroup.h index c79d870dc8b..8f76c34478f 100644 --- a/include/swift/ABI/TaskGroup.h +++ b/include/swift/ABI/TaskGroup.h @@ -39,6 +39,9 @@ public: /// Upon a future task's completion, offer it to the task group it belongs to. void offer(AsyncTask *completed, AsyncContext *context); + + /// Checks the cancellation status of the group. + bool isCancelled(); }; } // end namespace swift diff --git a/stdlib/public/Concurrency/Task.cpp b/stdlib/public/Concurrency/Task.cpp index 571a8f92b51..8a8fa5b7342 100644 --- a/stdlib/public/Concurrency/Task.cpp +++ b/stdlib/public/Concurrency/Task.cpp @@ -565,7 +565,8 @@ static AsyncTaskAndContext swift_task_create_group_future_commonImpl( // In a task group we would not have allowed the `add` to create a child anymore, // however better safe than sorry and `async let` are not expressed as task groups, // so they may have been spawned in any case still. - if (swift_task_isCancelled(parent)) + if (swift_task_isCancelled(parent) || + (group && group->isCancelled())) swift_task_cancel(task); // Initialize task locals with a link to the parent task. diff --git a/stdlib/public/Concurrency/TaskGroup.cpp b/stdlib/public/Concurrency/TaskGroup.cpp index ceb951f0599..dd66391127e 100644 --- a/stdlib/public/Concurrency/TaskGroup.cpp +++ b/stdlib/public/Concurrency/TaskGroup.cpp @@ -508,6 +508,10 @@ void TaskGroup::offer(AsyncTask *completedTask, AsyncContext *context) { asImpl(this)->offer(completedTask, context); } +bool TaskGroup::isCancelled() { + return asImpl(this)->isCancelled(); +} + static void fillGroupNextResult(TaskFutureWaitAsyncContext *context, PollResult result) { /// Fill in the result value diff --git a/stdlib/public/Concurrency/TaskGroup.swift b/stdlib/public/Concurrency/TaskGroup.swift index fa3cbfc8d77..a4f91c7a957 100644 --- a/stdlib/public/Concurrency/TaskGroup.swift +++ b/stdlib/public/Concurrency/TaskGroup.swift @@ -355,12 +355,12 @@ public struct TaskGroup { _taskGroupIsEmpty(_group) } - /// Cancel all the remaining tasks in the group. + /// Cancel all the remaining, and future, tasks in the group. /// - /// A cancelled group will not will NOT accept new tasks being added into it. - /// - /// Any results, including errors thrown by tasks affected by this - /// cancellation, are silently discarded. + /// A cancelled group will not will create new tasks when the `asyncUnlessCancelled`, + /// function is used. It will, however, continue to create tasks when the plain `async` + /// function is used. Such tasks will be created yet immediately cancelled, allowing + /// the tasks to perform some short-cut implementation, if they are responsive to cancellation. /// /// This function may be called even from within child (or any other) tasks, /// and will reliably cause the group to become cancelled. diff --git a/test/Concurrency/Runtime/async_taskgroup_cancel_then_spawn.swift b/test/Concurrency/Runtime/async_taskgroup_cancel_then_spawn.swift index 8c3b16a02ff..f26af3f6637 100644 --- a/test/Concurrency/Runtime/async_taskgroup_cancel_then_spawn.swift +++ b/test/Concurrency/Runtime/async_taskgroup_cancel_then_spawn.swift @@ -37,13 +37,16 @@ func test_taskGroup_cancel_then_add() async { let none = await group.next() print("next second: \(none)") // CHECK: next second: nil - group.spawn { 3 } - print("added third, unconditionally") // CHECK: added third, unconditionally - print("group isCancelled: \(group.isCancelled)") // CHECK: group isCancelled: true - + group.spawn { + print("child task isCancelled: \(Task.isCancelled)") // CHECK: child task isCancelled: true + return 3 + } let three = await group.next()! print("next third: \(three)") // CHECK: next third: 3 + print("added third, unconditionally") // CHECK: added third, unconditionally + print("group isCancelled: \(group.isCancelled)") // CHECK: group isCancelled: true + return one + (none ?? 0) }