Skip to content

Commit 8d24edd

Browse files
committed
Add extensions to TaskGroup and ThrowingTaskGroup
1 parent b50e930 commit 8d24edd

File tree

2 files changed

+119
-1
lines changed

2 files changed

+119
-1
lines changed

Sources/ServiceLifecycle/GracefulShutdown.swift

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ extension Task {
4848
/// Indicates that the task should shutdown gracefully.
4949
///
5050
/// Calling this method on a task that doesn’t support graceful shutdown has no effect.
51-
/// Likewise, if the task has already run past the last point where it would stop early, calling this method has no effect.
5251
///
5352
/// - Note: This method is mostly relevant for testing graceful shutdown. In your application, the ``ServiceRunner``
5453
/// should be the instance that triggers the graceful shutdown.
@@ -62,6 +61,40 @@ extension Task {
6261
}
6362
}
6463

64+
extension TaskGroup {
65+
/// Indicates all of the tasks in the group to shutdown gracefully.
66+
///
67+
/// Calling this method on a task that doesn’t support graceful shutdown has no effect.
68+
///
69+
/// - Note: This method is mostly relevant for testing graceful shutdown. In your application, the ``ServiceRunner``
70+
/// should be the instance that triggers the graceful shutdown.
71+
public func shutdownGracefullyAll() async {
72+
guard let gracefulShutdownManager = TaskLocals.gracefulShutdownManager else {
73+
print("WARNING: Trying to shutdown gracefully inside a task that doesn't have access to the ShutdownGracefulManager. This happens either when unstructured Concurrency is used like Task.detached {} or when you tried to shutdown gracefully outside the ServiceRunner.run method.")
74+
return
75+
}
76+
77+
await gracefulShutdownManager.shutdownGracefully()
78+
}
79+
}
80+
81+
extension ThrowingTaskGroup {
82+
/// Indicates all of the tasks in the group to shutdown gracefully.
83+
///
84+
/// Calling this method on a task that doesn’t support graceful shutdown has no effect.
85+
///
86+
/// - Note: This method is mostly relevant for testing graceful shutdown. In your application, the ``ServiceRunner``
87+
/// should be the instance that triggers the graceful shutdown.
88+
public func shutdownGracefullyAll() async {
89+
guard let gracefulShutdownManager = TaskLocals.gracefulShutdownManager else {
90+
print("WARNING: Trying to shutdown gracefully inside a task that doesn't have access to the ShutdownGracefulManager. This happens either when unstructured Concurrency is used like Task.detached {} or when you tried to shutdown gracefully outside the ServiceRunner.run method.")
91+
return
92+
}
93+
94+
await gracefulShutdownManager.shutdownGracefully()
95+
}
96+
}
97+
6598
@_spi(Testing)
6699
public actor GracefulShutdownManager {
67100
struct Handler {

Tests/ServiceLifecycleTests/GracefulShutdownTests.swift

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,4 +134,89 @@ final class GracefulShutdownTests: XCTestCase {
134134
XCTAssertNil(weakFoo)
135135
}
136136
}
137+
138+
func testTaskShutdownGracefully() async {
139+
let shutdownGracefulManager = GracefulShutdownManager()
140+
await TaskLocals.$gracefulShutdownManager.withValue(shutdownGracefulManager) {
141+
let task = Task {
142+
var cont: AsyncStream<Void>.Continuation!
143+
let stream = AsyncStream<Void> { cont = $0 }
144+
let continuation = cont!
145+
146+
await withShutdownGracefulHandler {
147+
await withTaskGroup(of: Void.self) { group in
148+
group.addTask {
149+
await stream.first { _ in true }
150+
}
151+
152+
await group.waitForAll()
153+
}
154+
} onGracefulShutdown: {
155+
continuation.finish()
156+
}
157+
}
158+
159+
await task.shutdownGracefully()
160+
161+
await task.value
162+
}
163+
}
164+
165+
func testTaskGroupShutdownGracefully() async {
166+
let shutdownGracefulManager = GracefulShutdownManager()
167+
await TaskLocals.$gracefulShutdownManager.withValue(shutdownGracefulManager) {
168+
await withTaskGroup(of: Void.self) { group in
169+
group.addTask {
170+
var cont: AsyncStream<Void>.Continuation!
171+
let stream = AsyncStream<Void> { cont = $0 }
172+
let continuation = cont!
173+
174+
await withShutdownGracefulHandler {
175+
await withTaskGroup(of: Void.self) { group in
176+
group.addTask {
177+
await stream.first { _ in true }
178+
}
179+
180+
await group.waitForAll()
181+
}
182+
} onGracefulShutdown: {
183+
continuation.finish()
184+
}
185+
}
186+
187+
await group.shutdownGracefullyAll()
188+
189+
await group.waitForAll()
190+
}
191+
}
192+
}
193+
194+
func testThrowingTaskGroupShutdownGracefully() async {
195+
let shutdownGracefulManager = GracefulShutdownManager()
196+
await TaskLocals.$gracefulShutdownManager.withValue(shutdownGracefulManager) {
197+
await withThrowingTaskGroup(of: Void.self) { group in
198+
group.addTask {
199+
var cont: AsyncStream<Void>.Continuation!
200+
let stream = AsyncStream<Void> { cont = $0 }
201+
let continuation = cont!
202+
203+
await withShutdownGracefulHandler {
204+
await withTaskGroup(of: Void.self) { group in
205+
group.addTask {
206+
await stream.first { _ in true }
207+
}
208+
209+
await group.waitForAll()
210+
}
211+
} onGracefulShutdown: {
212+
continuation.finish()
213+
}
214+
}
215+
216+
await group.shutdownGracefullyAll()
217+
218+
try! await group.waitForAll()
219+
}
220+
}
221+
}
137222
}

0 commit comments

Comments
 (0)