Skip to content

Commit 3295ddd

Browse files
authored
[Concurrency] waitForAll and next of TaskGroups must inherit isolation (#72794)
1 parent 129a021 commit 3295ddd

File tree

5 files changed

+119
-10
lines changed

5 files changed

+119
-10
lines changed

stdlib/public/Concurrency/TaskGroup.swift

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ public func withTaskGroup<ChildTaskResult, GroupResult>(
8080
// Run the withTaskGroup body.
8181
let result = await body(&group)
8282

83+
// TODO(concurrency): should get isolation from param from withThrowingTaskGroup
8384
await group.awaitAllRemainingTasks()
8485

8586
Builtin.destroyTaskGroup(_group)
@@ -183,13 +184,15 @@ public func withThrowingTaskGroup<ChildTaskResult, GroupResult>(
183184
// Run the withTaskGroup body.
184185
let result = try await body(&group)
185186

187+
// TODO(concurrency): should get isolation from param from withThrowingTaskGroup
186188
await group.awaitAllRemainingTasks()
187189
Builtin.destroyTaskGroup(_group)
188190

189191
return result
190192
} catch {
191193
group.cancelAll()
192194

195+
// TODO(concurrency): should get isolation from param from withThrowingTaskGroup
193196
await group.awaitAllRemainingTasks()
194197
Builtin.destroyTaskGroup(_group)
195198

@@ -563,22 +566,41 @@ public struct TaskGroup<ChildTaskResult: Sendable> {
563566
/// that method can't be called from a concurrent execution context like a child task.
564567
///
565568
/// - Returns: The value returned by the next child task that completes.
566-
public mutating func next() async -> ChildTaskResult? {
569+
@available(SwiftStdlib 5.1, *)
570+
@backDeployed(before: SwiftStdlib 6.0)
571+
public mutating func next(isolation: isolated (any Actor)? = #isolation) async -> ChildTaskResult? {
572+
// try!-safe because this function only exists for Failure == Never,
573+
// and as such, it is impossible to spawn a throwing child task.
574+
return try! await _taskGroupWaitNext(group: _group) // !-safe cannot throw, we're a non-throwing TaskGroup
575+
}
576+
577+
@usableFromInline
578+
@available(SwiftStdlib 5.1, *)
579+
@_silgen_name("$sScG4nextxSgyYaF")
580+
internal mutating func __abi_next() async -> ChildTaskResult? {
567581
// try!-safe because this function only exists for Failure == Never,
568582
// and as such, it is impossible to spawn a throwing child task.
569583
return try! await _taskGroupWaitNext(group: _group) // !-safe cannot throw, we're a non-throwing TaskGroup
570584
}
571585

572586
/// Await all of the pending tasks added this group.
573587
@usableFromInline
588+
@available(SwiftStdlib 5.1, *)
589+
@backDeployed(before: SwiftStdlib 6.0)
590+
internal mutating func awaitAllRemainingTasks(isolation: isolated (any Actor)? = #isolation) async {
591+
while let _ = await next(isolation: isolation) {}
592+
}
593+
594+
@usableFromInline
595+
@available(SwiftStdlib 5.1, *)
574596
internal mutating func awaitAllRemainingTasks() async {
575-
while let _ = await next() {}
597+
while let _ = await next(isolation: nil) {}
576598
}
577599

578600
/// Wait for all of the group's remaining tasks to complete.
579601
@_alwaysEmitIntoClient
580-
public mutating func waitForAll() async {
581-
await awaitAllRemainingTasks()
602+
public mutating func waitForAll(isolation: isolated (any Actor)? = #isolation) async {
603+
await awaitAllRemainingTasks(isolation: isolation)
582604
}
583605

584606
/// A Boolean value that indicates whether the group has any remaining tasks.
@@ -703,16 +725,24 @@ public struct ThrowingTaskGroup<ChildTaskResult: Sendable, Failure: Error> {
703725

704726
/// Await all the remaining tasks on this group.
705727
@usableFromInline
706-
internal mutating func awaitAllRemainingTasks() async {
728+
@available(SwiftStdlib 5.1, *)
729+
@backDeployed(before: SwiftStdlib 6.0)
730+
internal mutating func awaitAllRemainingTasks(isolation: isolated (any Actor)? = #isolation) async {
707731
while true {
708732
do {
709-
guard let _ = try await next() else {
733+
guard let _ = try await next(isolation: isolation) else {
710734
return
711735
}
712736
} catch {}
713737
}
714738
}
715739

740+
@usableFromInline
741+
@available(SwiftStdlib 5.1, *)
742+
internal mutating func awaitAllRemainingTasks() async {
743+
await awaitAllRemainingTasks(isolation: nil)
744+
}
745+
716746
@usableFromInline
717747
internal mutating func _waitForAll() async throws {
718748
await self.awaitAllRemainingTasks()
@@ -750,7 +780,7 @@ public struct ThrowingTaskGroup<ChildTaskResult: Sendable, Failure: Error> {
750780
/// - Throws: The *first* error that was thrown by a child task during draining all the tasks.
751781
/// This first error is stored until all other tasks have completed, and is re-thrown afterwards.
752782
@_alwaysEmitIntoClient
753-
public mutating func waitForAll() async throws {
783+
public mutating func waitForAll(isolation: isolated (any Actor)? = #isolation) async throws {
754784
var firstError: Error? = nil
755785

756786
// Make sure we loop until all child tasks have completed
@@ -999,7 +1029,16 @@ public struct ThrowingTaskGroup<ChildTaskResult: Sendable, Failure: Error> {
9991029
/// - Throws: The error thrown by the next child task that completes.
10001030
///
10011031
/// - SeeAlso: `nextResult()`
1002-
public mutating func next() async throws -> ChildTaskResult? {
1032+
@available(SwiftStdlib 5.1, *)
1033+
@backDeployed(before: SwiftStdlib 6.0)
1034+
public mutating func next(isolation: isolated (any Actor)? = #isolation) async throws -> ChildTaskResult? {
1035+
return try await _taskGroupWaitNext(group: _group)
1036+
}
1037+
1038+
@usableFromInline
1039+
@available(SwiftStdlib 5.1, *)
1040+
@_silgen_name("$sScg4nextxSgyYaKF")
1041+
internal mutating func __abi_next() async throws -> ChildTaskResult? {
10031042
return try await _taskGroupWaitNext(group: _group)
10041043
}
10051044

@@ -1052,7 +1091,7 @@ public struct ThrowingTaskGroup<ChildTaskResult: Sendable, Failure: Error> {
10521091
///
10531092
/// - SeeAlso: `next()`
10541093
@_alwaysEmitIntoClient
1055-
public mutating func nextResult() async -> Result<ChildTaskResult, Failure>? {
1094+
public mutating func nextResult(isolation: isolated (any Actor)? = #isolation) async -> Result<ChildTaskResult, Failure>? {
10561095
return try! await nextResultForABI()
10571096
}
10581097

@@ -1332,7 +1371,7 @@ func _taskGroupIsCancelled(group: Builtin.RawPointer) -> Bool
13321371

13331372
@available(SwiftStdlib 5.1, *)
13341373
@_silgen_name("swift_taskGroup_wait_next_throwing")
1335-
func _taskGroupWaitNext<T>(group: Builtin.RawPointer) async throws -> T?
1374+
public func _taskGroupWaitNext<T>(group: Builtin.RawPointer) async throws -> T?
13361375

13371376
@available(SwiftStdlib 5.1, *)
13381377
@_silgen_name("swift_task_hasTaskGroupStatusRecord")
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// RUN: %target-swift-frontend -disable-availability-checking %s -emit-sil -o /dev/null -verify -strict-concurrency=complete -enable-upcoming-feature RegionBasedIsolation
2+
3+
// REQUIRES: concurrency
4+
// REQUIRES: asserts
5+
// REQUIRES: libdispatch
6+
7+
@MainActor
8+
class MyActor {
9+
func check() async throws {
10+
await withTaskGroup(of: Int.self) { group in
11+
group.addTask {
12+
2
13+
}
14+
await group.waitForAll()
15+
}
16+
17+
try await withThrowingTaskGroup(of: Int.self) { throwingGroup in
18+
throwingGroup.addTask {
19+
2
20+
}
21+
try await throwingGroup.waitForAll()
22+
}
23+
24+
await withDiscardingTaskGroup { discardingGroup in
25+
discardingGroup.addTask {
26+
()
27+
}
28+
}
29+
30+
try await withThrowingDiscardingTaskGroup { throwingDiscardingGroup in
31+
throwingDiscardingGroup.addTask {
32+
()
33+
}
34+
}
35+
}
36+
}

test/abi/macOS/arm64/concurrency.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,19 @@ Added: _$ss26withTaskExecutorPreference_9isolation9operationxSch_pSg_ScA_pSgYixy
276276
// async function pointer to Swift.withTaskExecutorPreference<A, B where B: Swift.Error>(_: Swift.TaskExecutor?, isolation: isolated Swift.Actor?, operation: () async throws(B) -> A) async throws(B) -> A
277277
Added: _$ss26withTaskExecutorPreference_9isolation9operationxSch_pSg_ScA_pSgYixyYaq_YKXEtYaq_YKs5ErrorR_r0_lFTu
278278

279+
// === Add #isolation to next() and waitForAll() in task groups
280+
// Swift.TaskGroup.awaitAllRemainingTasks(isolation: isolated Swift.Actor?) async -> ()
281+
Added: _$sScG22awaitAllRemainingTasks9isolationyScA_pSgYi_tYaF
282+
Added: _$sScG22awaitAllRemainingTasks9isolationyScA_pSgYi_tYaFTu
283+
// Swift.TaskGroup.next(isolation: isolated Swift.Actor?) async -> A?
284+
Added: _$sScG4next9isolationxSgScA_pSgYi_tYaF
285+
Added: _$sScG4next9isolationxSgScA_pSgYi_tYaFTu
286+
// Swift.ThrowingTaskGroup.next(isolation: isolated Swift.Actor?) async throws -> A?
287+
Added: _$sScg4next9isolationxSgScA_pSgYi_tYaKF
288+
Added: _$sScg4next9isolationxSgScA_pSgYi_tYaKFTu
289+
// Swift.ThrowingTaskGroup.awaitAllRemainingTasks(isolation: isolated Swift.Actor?) async -> ()
290+
Added: _$sScg22awaitAllRemainingTasks9isolationyScA_pSgYi_tYaF
291+
Added: _$sScg22awaitAllRemainingTasks9isolationyScA_pSgYi_tYaFTu
279292

280293
// next() default implementation in terms of next(isolation:)
281294
Added: _$sScIsE4next7ElementQzSgyYa7FailureQzYKF

test/abi/macOS/x86_64/concurrency.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,19 @@ Added: _$ss26withTaskExecutorPreference_9isolation9operationxSch_pSg_ScA_pSgYixy
276276
// async function pointer to Swift.withTaskExecutorPreference<A, B where B: Swift.Error>(_: Swift.TaskExecutor?, isolation: isolated Swift.Actor?, operation: () async throws(B) -> A) async throws(B) -> A
277277
Added: _$ss26withTaskExecutorPreference_9isolation9operationxSch_pSg_ScA_pSgYixyYaq_YKXEtYaq_YKs5ErrorR_r0_lFTu
278278

279+
// === Add #isolation to next() and waitForAll() in task groups
280+
// Swift.TaskGroup.awaitAllRemainingTasks(isolation: isolated Swift.Actor?) async -> ()
281+
Added: _$sScG22awaitAllRemainingTasks9isolationyScA_pSgYi_tYaF
282+
Added: _$sScG22awaitAllRemainingTasks9isolationyScA_pSgYi_tYaFTu
283+
// Swift.TaskGroup.next(isolation: isolated Swift.Actor?) async -> A?
284+
Added: _$sScG4next9isolationxSgScA_pSgYi_tYaF
285+
Added: _$sScG4next9isolationxSgScA_pSgYi_tYaFTu
286+
// Swift.ThrowingTaskGroup.next(isolation: isolated Swift.Actor?) async throws -> A?
287+
Added: _$sScg4next9isolationxSgScA_pSgYi_tYaKF
288+
Added: _$sScg4next9isolationxSgScA_pSgYi_tYaKFTu
289+
// Swift.ThrowingTaskGroup.awaitAllRemainingTasks(isolation: isolated Swift.Actor?) async -> ()
290+
Added: _$sScg22awaitAllRemainingTasks9isolationyScA_pSgYi_tYaF
291+
Added: _$sScg22awaitAllRemainingTasks9isolationyScA_pSgYi_tYaFTu
279292

280293
// next() default implementation in terms of next(isolation:)
281294
Added: _$sScIsE4next7ElementQzSgyYa7FailureQzYKF

test/api-digester/stability-concurrency-abi.test

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,14 @@ Func Executor.enqueue(_:) is a new API without @available attribute
9090
// This function correctly inherits its availability from the TaskLocal struct.
9191
Func TaskLocal.withValueImpl(_:operation:file:line:) is a new API without @available attribute
9292

93+
// The method is actually still there: '__abi_next' silgen_name("$sScG4nextxSgyYaF")
94+
Func TaskGroup.next() has been renamed to Func next(isolation:)
95+
Func TaskGroup.next() has mangled name changing from 'Swift.TaskGroup.next() async -> Swift.Optional<A>' to 'Swift.TaskGroup.next(isolation: isolated Swift.Optional<Swift.Actor>) async -> Swift.Optional<A>'
96+
97+
// The method is actually still there: '__abi_next' silgen_name("$sScg4nextxSgyYaKF")
98+
Func ThrowingTaskGroup.next() has been renamed to Func next(isolation:)
99+
Func ThrowingTaskGroup.next() has mangled name changing from 'Swift.ThrowingTaskGroup.next() async throws -> Swift.Optional<A>' to 'Swift.ThrowingTaskGroup.next(isolation: isolated Swift.Optional<Swift.Actor>) async throws -> Swift.Optional<A>'
100+
93101
// *** DO NOT DISABLE OR XFAIL THIS TEST. *** (See comment above.)
94102

95103

0 commit comments

Comments
 (0)