Skip to content

Commit a6be5e0

Browse files
committed
[Concurrency] waitForAll and next of TaskGroups must inherit isolation
Otherwise we get warnings when task groups are used within an actor. Add global actor tests to cover the specific situation. resolves rdar://122846553 futher ABI safeguarding
1 parent bef8b61 commit a6be5e0

File tree

5 files changed

+120
-10
lines changed

5 files changed

+120
-10
lines changed

stdlib/public/Concurrency/TaskGroup.swift

Lines changed: 50 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,42 @@ 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, *)
596+
@_silgen_name("$sScG22awaitAllRemainingTasksyyYaF")
574597
internal mutating func awaitAllRemainingTasks() async {
575-
while let _ = await next() {}
598+
while let _ = await next(isolation: nil) {}
576599
}
577600

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

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

704727
/// Await all the remaining tasks on this group.
705728
@usableFromInline
706-
internal mutating func awaitAllRemainingTasks() async {
729+
@available(SwiftStdlib 5.1, *)
730+
@backDeployed(before: SwiftStdlib 6.0)
731+
internal mutating func awaitAllRemainingTasks(isolation: isolated (any Actor)? = #isolation) async {
707732
while true {
708733
do {
709-
guard let _ = try await next() else {
734+
guard let _ = try await next(isolation: isolation) else {
710735
return
711736
}
712737
} catch {}
713738
}
714739
}
715740

741+
@usableFromInline
742+
@available(SwiftStdlib 5.1, *)
743+
internal mutating func awaitAllRemainingTasks() async {
744+
await awaitAllRemainingTasks(isolation: nil)
745+
}
746+
716747
@usableFromInline
717748
internal mutating func _waitForAll() async throws {
718749
await self.awaitAllRemainingTasks()
@@ -750,7 +781,7 @@ public struct ThrowingTaskGroup<ChildTaskResult: Sendable, Failure: Error> {
750781
/// - Throws: The *first* error that was thrown by a child task during draining all the tasks.
751782
/// This first error is stored until all other tasks have completed, and is re-thrown afterwards.
752783
@_alwaysEmitIntoClient
753-
public mutating func waitForAll() async throws {
784+
public mutating func waitForAll(isolation: isolated (any Actor)? = #isolation) async throws {
754785
var firstError: Error? = nil
755786

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

@@ -1052,7 +1092,7 @@ public struct ThrowingTaskGroup<ChildTaskResult: Sendable, Failure: Error> {
10521092
///
10531093
/// - SeeAlso: `next()`
10541094
@_alwaysEmitIntoClient
1055-
public mutating func nextResult() async -> Result<ChildTaskResult, Failure>? {
1095+
public mutating func nextResult(isolation: isolated (any Actor)? = #isolation) async -> Result<ChildTaskResult, Failure>? {
10561096
return try! await nextResultForABI()
10571097
}
10581098

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

13331373
@available(SwiftStdlib 5.1, *)
13341374
@_silgen_name("swift_taskGroup_wait_next_throwing")
1335-
func _taskGroupWaitNext<T>(group: Builtin.RawPointer) async throws -> T?
1375+
public func _taskGroupWaitNext<T>(group: Builtin.RawPointer) async throws -> T?
13361376

13371377
@available(SwiftStdlib 5.1, *)
13381378
@_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
// XFAIL: noncopyable_generics

0 commit comments

Comments
 (0)