Skip to content

Commit 4b91350

Browse files
authored
Merge pull request #34604 from ktoso/wip-nurseries-are-groups
2 parents 8fa86d9 + 49c6ae3 commit 4b91350

File tree

4 files changed

+71
-64
lines changed

4 files changed

+71
-64
lines changed

include/swift/ABI/TaskStatus.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,8 @@ class DeadlineStatusRecord : public TaskStatusRecord {
124124
/// A status record which states that a task has one or
125125
/// more active child tasks.
126126
class ChildTaskStatusRecord : public TaskStatusRecord {
127-
/// FIXME: should this be an array? How are things like task
128-
/// nurseries supposed to actually manage this? Should it be
129-
/// atomically moodifiable?
127+
/// FIXME: should this be an array? How are things like task groups supposed
128+
/// to actually manage this? Should it be atomically modifiable?
130129
AsyncTask *FirstChild;
131130

132131
public:

stdlib/public/Concurrency/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ add_swift_target_library(swift_Concurrency ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} I
1919
_TimeTypes.swift
2020
TaskAlloc.cpp
2121
TaskStatus.cpp
22-
TaskNurseries.swift
22+
TaskGroup.swift
2323
Mutex.cpp
2424

2525
SWIFT_MODULE_DEPENDS_OSX Darwin

stdlib/public/Concurrency/TaskNurseries.swift renamed to stdlib/public/Concurrency/TaskGroup.swift

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,59 +13,67 @@
1313
import Swift
1414
@_implementationOnly import _SwiftConcurrencyShims
1515

16-
// ==== Task Nursery -----------------------------------------------------------
16+
// ==== Task Group -------------------------------------------------------------
1717

1818
extension Task {
1919

20-
/// Starts a new nursery which provides a scope in which a dynamic number of
20+
/// Starts a new task group which provides a scope in which a dynamic number of
2121
/// tasks may be spawned.
2222
///
23-
/// Tasks added to the nursery by `nursery.add()` will automatically be
24-
/// awaited on when the scope exits.
23+
/// Tasks added to the group by `group.add()` will automatically be awaited on
24+
/// when the scope exits. If the group exits by throwing, all added tasks will
25+
/// be cancelled and their results discarded.
2526
///
2627
/// ### Implicit awaiting
27-
/// When results of tasks added to the nursery need to be collected, one will
28-
/// gather task's results using the `while let result = await nursery.next() { ... }`
29-
/// pattern.
28+
/// When results of tasks added to the group need to be collected, one can
29+
/// gather their results using the following pattern:
30+
///
31+
/// while let result = await group.next() {
32+
/// // some accumulation logic (e.g. sum += result)
33+
/// }
3034
///
3135
/// ### Cancellation
32-
/// If any of the tasks throws the nursery and all of its tasks will be cancelled,
33-
/// and the error will be re-thrown by `withNursery`.
36+
/// If an error is thrown out of the task group, all of its remaining tasks
37+
/// will be cancelled and the `withGroup` call will rethrow that error.
38+
///
39+
/// Individual tasks throwing results in their corresponding `try group.next()`
40+
/// call throwing, giving a chance to handle individual errors or letting the
41+
/// error be rethrown by the group.
3442
///
3543
/// Postcondition:
36-
/// Once `withNursery` returns it is guaranteed that the *nursery* is *empty*.
44+
/// Once `withGroup` returns it is guaranteed that the `group` is *empty*.
3745
///
3846
/// This is achieved in the following way:
3947
/// - if the body returns normally:
40-
/// - the nursery will await any not yet complete tasks,
48+
/// - the group will await any not yet complete tasks,
4149
/// - if any of those tasks throws, the remaining tasks will be cancelled,
42-
/// - once the `withNursery` returns the nursery is guaranteed to be empty.
50+
/// - once the `withGroup` returns the group is guaranteed to be empty.
4351
/// - if the body throws:
44-
/// - all tasks remaining in the nursery will be automatically cancelled.
45-
///
46-
// TODO: Do we have to add a different nursery type to accommodate throwing
52+
/// - all tasks remaining in the group will be automatically cancelled.
53+
// TODO: Do we have to add a different group type to accommodate throwing
4754
// tasks without forcing users to use Result? I can't think of how that
4855
// could be propagated out of the callback body reasonably, unless we
4956
// commit to doing multi-statement closure typechecking.
50-
public static func withNursery<TaskResult, BodyResult>(
57+
public static func withGroup<TaskResult, BodyResult>(
5158
resultType: TaskResult.Type,
5259
returning returnType: BodyResult.Type = BodyResult.self,
53-
body: (inout Nursery<TaskResult>) async throws -> BodyResult
60+
body: (inout Task.Group<TaskResult>) async throws -> BodyResult
5461
) async rethrows -> BodyResult {
5562
fatalError("\(#function) not implemented yet.")
5663
}
5764

58-
/// A nursery provides a scope within which a dynamic number of tasks may be
59-
/// started and added to the nursery.
65+
/// A task group serves as storage for dynamically started tasks.
66+
///
67+
/// Its intended use is with the
6068
/* @unmoveable */
61-
public struct Nursery<TaskResult> {
69+
public struct Group<TaskResult> {
6270
/// No public initializers
6371
private init() {}
6472

6573
// Swift will statically prevent this type from being copied or moved.
6674
// For now, that implies that it cannot be used with generics.
6775

68-
/// Add a child task to the nursery.
76+
/// Add a child task to the group.
6977
///
7078
/// ### Error handling
7179
/// Operations are allowed to throw.
@@ -75,7 +83,7 @@ extension Task {
7583
///
7684
/// - Parameters:
7785
/// - overridingPriority: override priority of the operation task
78-
/// - operation: operation to execute and add to the nursery
86+
/// - operation: operation to execute and add to the group
7987
public mutating func add(
8088
overridingPriority: Priority? = nil,
8189
operation: () async throws -> TaskResult
@@ -86,11 +94,11 @@ extension Task {
8694
/// Add a child task and return a `Task.Handle` that can be used to manage it.
8795
///
8896
/// The task's result is accessible either via the returned `handle` or the
89-
/// `nursery.next()` function (as any other `add`-ed task).
97+
/// `group.next()` function (as any other `add`-ed task).
9098
///
9199
/// - Parameters:
92100
/// - overridingPriority: override priority of the operation task
93-
/// - operation: operation to execute and add to the nursery
101+
/// - operation: operation to execute and add to the group
94102
public mutating func addWithHandle(
95103
overridingPriority: Priority? = nil,
96104
operation: () async throws -> TaskResult
@@ -106,20 +114,20 @@ extension Task {
106114
fatalError("\(#function) not implemented yet.")
107115
}
108116

109-
/// Query whether the nursery has any remaining tasks.
117+
/// Query whether the group has any remaining tasks.
110118
///
111-
/// Nurseries are always empty upon entry to the `withNursery` body, and
112-
/// become empty again when `withNursery` returns (either by awaiting on all
119+
/// Task groups are always empty upon entry to the `withGroup` body, and
120+
/// become empty again when `withGroup` returns (either by awaiting on all
113121
/// pending tasks or cancelling them).
114122
///
115-
/// - Returns: `true` if the nursery has no pending tasks, `false` otherwise.
123+
/// - Returns: `true` if the group has no pending tasks, `false` otherwise.
116124
public var isEmpty: Bool {
117125
fatalError("\(#function) not implemented yet.")
118126
}
119127

120-
/// Cancel all the remaining tasks in the nursery.
128+
/// Cancel all the remaining tasks in the group.
121129
///
122-
/// A cancelled nursery will not will NOT accept new tasks being added into it.
130+
/// A cancelled group will not will NOT accept new tasks being added into it.
123131
///
124132
/// Any results, including errors thrown by tasks affected by this
125133
/// cancellation, are silently discarded.

test/Concurrency/async_nurseries.swift renamed to test/Concurrency/async_task_groups.swift

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,31 +13,31 @@ func asyncThrowsOnCancel() async throws -> Int {
1313
throw Task.CancellationError()
1414
}
1515

16-
func test_nursery_add() async throws -> Int {
17-
await try Task.withNursery(resultType: Int.self) { nursery in
18-
await nursery.add {
16+
func test_taskGroup_add() async throws -> Int {
17+
await try Task.withGroup(resultType: Int.self) { group in
18+
await group.add {
1919
await asyncFunc()
2020
}
2121

22-
await nursery.add {
22+
await group.add {
2323
await asyncFunc()
2424
}
2525

2626
var sum = 0
27-
while let v = await try nursery.next() {
27+
while let v = await try group.next() {
2828
sum += v
2929
}
3030
return sum
3131
} // implicitly awaits
3232
}
3333

34-
func test_nursery_addHandles() async throws -> Int {
35-
await try Task.withNursery(resultType: Int.self) { nursery in
36-
let one = await nursery.addWithHandle {
34+
func test_taskGroup_addHandles() async throws -> Int {
35+
await try Task.withGroup(resultType: Int.self) { group in
36+
let one = await group.addWithHandle {
3737
await asyncFunc()
3838
}
3939

40-
let two = await nursery.addWithHandle {
40+
let two = await group.addWithHandle {
4141
await asyncFunc()
4242
}
4343

@@ -46,13 +46,13 @@ func test_nursery_addHandles() async throws -> Int {
4646
} // implicitly awaits
4747
}
4848

49-
func test_nursery_cancel_handles() async throws {
50-
await try Task.withNursery(resultType: Int.self) { nursery in
51-
let one = await nursery.addWithHandle {
49+
func test_taskGroup_cancel_handles() async throws {
50+
await try Task.withGroup(resultType: Int.self) { group in
51+
let one = await group.addWithHandle {
5252
await try asyncThrowsOnCancel()
5353
}
5454

55-
let two = await nursery.addWithHandle {
55+
let two = await group.addWithHandle {
5656
await asyncFunc()
5757
}
5858

@@ -62,20 +62,20 @@ func test_nursery_cancel_handles() async throws {
6262
}
6363

6464
// ==== ------------------------------------------------------------------------
65-
// MARK: Example Nursery Usages
65+
// MARK: Example group Usages
6666

6767
struct Boom: Error {}
6868
func work() async -> Int { 42 }
6969
func boom() async throws -> Int { throw Boom() }
7070

7171
func first_allMustSucceed() async throws {
7272

73-
let first: Int = await try Task.withNursery(resultType: Int.self) { nursery in
74-
await nursery.add { await work() }
75-
await nursery.add { await work() }
76-
await nursery.add { await try boom() }
73+
let first: Int = await try Task.withGroup(resultType: Int.self) { group in
74+
await group.add { await work() }
75+
await group.add { await work() }
76+
await group.add { await try boom() }
7777

78-
if let first = await try nursery.next() {
78+
if let first = await try group.next() {
7979
return first
8080
} else {
8181
fatalError("Should never happen, we either throw, or get a result from any of the tasks")
@@ -90,10 +90,10 @@ func first_ignoreFailures() async throws {
9090
func work() async -> Int { 42 }
9191
func boom() async throws -> Int { throw Boom() }
9292

93-
let first: Int = await try Task.withNursery(resultType: Int.self) { nursery in
94-
await nursery.add { await work() }
95-
await nursery.add { await work() }
96-
await nursery.add {
93+
let first: Int = await try Task.withGroup(resultType: Int.self) { group in
94+
await group.add { await work() }
95+
await group.add { await work() }
96+
await group.add {
9797
do {
9898
return await try boom()
9999
} catch {
@@ -102,7 +102,7 @@ func first_ignoreFailures() async throws {
102102
}
103103

104104
var result: Int = 0
105-
while let v = await try nursery.next() {
105+
while let v = await try group.next() {
106106
result = v
107107

108108
if result != 0 {
@@ -117,9 +117,9 @@ func first_ignoreFailures() async throws {
117117
}
118118

119119
// ==== ------------------------------------------------------------------------
120-
// MARK: Advanced Custom Nursery Usage
120+
// MARK: Advanced Custom Task Group Usage
121121

122-
func test_nursery_quorum_thenCancel() async {
122+
func test_taskGroup_quorum_thenCancel() async {
123123
// imitates a typical "gather quorum" routine that is typical in distributed systems programming
124124
enum Vote {
125125
case yay
@@ -137,19 +137,19 @@ func test_nursery_quorum_thenCancel() async {
137137
///
138138
/// - Returns: `true` iff `N/2 + 1` followers return `.yay`, `false` otherwise.
139139
func gatherQuorum(followers: [Follower]) async -> Bool {
140-
await try! Task.withNursery(resultType: Vote.self) { nursery in
140+
await try! Task.withGroup(resultType: Vote.self) { group in
141141
for follower in followers {
142-
await nursery.add { await try follower.vote() }
142+
await group.add { await try follower.vote() }
143143
}
144144

145145
defer {
146-
nursery.cancelAll()
146+
group.cancelAll()
147147
}
148148

149149
var yays: Int = 0
150150
var nays: Int = 0
151151
let quorum = Int(followers.count / 2) + 1
152-
while let vote = await try nursery.next() {
152+
while let vote = await try group.next() {
153153
switch vote {
154154
case .yay:
155155
yays += 1

0 commit comments

Comments
 (0)