Skip to content

Commit 27a8fe8

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
1 parent ffa8c9a commit 27a8fe8

File tree

4 files changed

+109
-22
lines changed

4 files changed

+109
-22
lines changed

stdlib/public/Concurrency/TaskGroup.swift

Lines changed: 47 additions & 22 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,35 @@ 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+
public mutating func next(isolation: isolated (any Actor)? = #isolation) async -> ChildTaskResult? {
570+
// try!-safe because this function only exists for Failure == Never,
571+
// and as such, it is impossible to spawn a throwing child task.
572+
return try! await _taskGroupWaitNext(group: _group) // !-safe cannot throw, we're a non-throwing TaskGroup
573+
}
574+
575+
@usableFromInline
576+
@_silgen_name("$sScG4nextxSgyYaF")
577+
internal mutating func __abi_next() async -> ChildTaskResult? {
567578
// try!-safe because this function only exists for Failure == Never,
568579
// and as such, it is impossible to spawn a throwing child task.
569580
return try! await _taskGroupWaitNext(group: _group) // !-safe cannot throw, we're a non-throwing TaskGroup
570581
}
571582

572583
/// Await all of the pending tasks added this group.
573584
@usableFromInline
574-
internal mutating func awaitAllRemainingTasks() async {
575-
while let _ = await next() {}
585+
internal mutating func awaitAllRemainingTasks(isolation: isolated (any Actor)? = #isolation) async {
586+
while let _ = await next(isolation: isolation) {}
587+
}
588+
@usableFromInline
589+
@_silgen_name("$sScG22awaitAllRemainingTasksyyYaF")
590+
internal mutating func __abi_awaitAllRemainingTasks() async {
591+
while let _ = await next(isolation: nil) {}
576592
}
577593

578594
/// Wait for all of the group's remaining tasks to complete.
579595
@_alwaysEmitIntoClient
580-
public mutating func waitForAll() async {
581-
await awaitAllRemainingTasks()
596+
public mutating func waitForAll(isolation: isolated (any Actor)? = #isolation) async {
597+
await awaitAllRemainingTasks(isolation: isolation)
582598
}
583599

584600
/// A Boolean value that indicates whether the group has any remaining tasks.
@@ -703,15 +719,19 @@ public struct ThrowingTaskGroup<ChildTaskResult: Sendable, Failure: Error> {
703719

704720
/// Await all the remaining tasks on this group.
705721
@usableFromInline
706-
internal mutating func awaitAllRemainingTasks() async {
722+
internal mutating func awaitAllRemainingTasks(isolation: isolated (any Actor)? = #isolation) async {
707723
while true {
708724
do {
709-
guard let _ = try await next() else {
725+
guard let _ = try await next(isolation: isolation) else {
710726
return
711727
}
712728
} catch {}
713729
}
714730
}
731+
@usableFromInline
732+
internal mutating func awaitAllRemainingTasks() async {
733+
await awaitAllRemainingTasks(isolation: nil)
734+
}
715735

716736
@usableFromInline
717737
internal mutating func _waitForAll() async throws {
@@ -750,7 +770,7 @@ public struct ThrowingTaskGroup<ChildTaskResult: Sendable, Failure: Error> {
750770
/// - Throws: The *first* error that was thrown by a child task during draining all the tasks.
751771
/// This first error is stored until all other tasks have completed, and is re-thrown afterwards.
752772
@_alwaysEmitIntoClient
753-
public mutating func waitForAll() async throws {
773+
public mutating func waitForAll(isolation: isolated (any Actor)? = #isolation) async throws {
754774
var firstError: Error? = nil
755775

756776
// Make sure we loop until all child tasks have completed
@@ -999,22 +1019,14 @@ public struct ThrowingTaskGroup<ChildTaskResult: Sendable, Failure: Error> {
9991019
/// - Throws: The error thrown by the next child task that completes.
10001020
///
10011021
/// - SeeAlso: `nextResult()`
1002-
public mutating func next() async throws -> ChildTaskResult? {
1022+
public mutating func next(isolation: isolated (any Actor)? = #isolation) async throws -> ChildTaskResult? {
10031023
return try await _taskGroupWaitNext(group: _group)
10041024
}
10051025

1006-
@_silgen_name("$sScg10nextResults0B0Oyxq_GSgyYaKF")
10071026
@usableFromInline
1008-
mutating func nextResultForABI() async throws -> Result<ChildTaskResult, Failure>? {
1009-
do {
1010-
guard let success: ChildTaskResult = try await _taskGroupWaitNext(group: _group) else {
1011-
return nil
1012-
}
1013-
1014-
return .success(success)
1015-
} catch {
1016-
return .failure(error as! Failure) // as!-safe, because we are only allowed to throw Failure (Error)
1017-
}
1027+
@_silgen_name("$sScg4nextxSgyYaKF")
1028+
internal mutating func __abi_next() async throws -> ChildTaskResult? {
1029+
return try await _taskGroupWaitNext(group: _group)
10181030
}
10191031

10201032
/// Wait for the next child task to complete,
@@ -1052,10 +1064,23 @@ public struct ThrowingTaskGroup<ChildTaskResult: Sendable, Failure: Error> {
10521064
///
10531065
/// - SeeAlso: `next()`
10541066
@_alwaysEmitIntoClient
1055-
public mutating func nextResult() async -> Result<ChildTaskResult, Failure>? {
1056-
return try! await nextResultForABI()
1067+
public mutating func nextResult(isolation: isolated (any Actor)? = #isolation) async -> Result<ChildTaskResult, Failure>? {
1068+
return try! await __abi_nextResult()
10571069
}
10581070

1071+
@_silgen_name("$sScg10nextResults0B0Oyxq_GSgyYaKF")
1072+
@usableFromInline
1073+
mutating func __abi_nextResult() async throws -> Result<ChildTaskResult, Failure>? {
1074+
do {
1075+
guard let success: ChildTaskResult = try await _taskGroupWaitNext(group: _group) else {
1076+
return nil
1077+
}
1078+
1079+
return .success(success)
1080+
} catch {
1081+
return .failure(error as! Failure) // as!-safe, because we are only allowed to throw Failure (Error)
1082+
}
1083+
}
10591084
/// A Boolean value that indicates whether the group has any remaining tasks.
10601085
///
10611086
/// At the start of the body of a `withThrowingTaskGroup(of:returning:body:)` call,
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

0 commit comments

Comments
 (0)