Skip to content

Commit 81f64d9

Browse files
committed
Add waitForGracefulShutdown
1 parent b69c630 commit 81f64d9

File tree

2 files changed

+52
-0
lines changed

2 files changed

+52
-0
lines changed

Sources/ServiceLifecycle/GracefulShutdown.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,18 @@ public func withGracefulShutdownHandler<T>(
5555
return try await operation()
5656
}
5757

58+
/// Suspend the current task until graceful shutdown is executed.
59+
///
60+
/// A common use-case is to call this method in the `operation` closure of ``withGracefulShutdownHandler(operation:onGracefulShutdown:)``
61+
/// when you are only interested in executing a closure upon graceful shutdown, and nothing else.
62+
public func waitForGracefulShutdown() async {
63+
guard let gracefulShutdownManager = TaskLocals.gracefulShutdownManager else {
64+
return
65+
}
66+
67+
await gracefulShutdownManager.waitForGracefulShutdown()
68+
}
69+
5870
/// This is just a helper type for the result of our task group.
5971
enum ValueOrGracefulShutdown<T: Sendable>: Sendable {
6072
case value(T)
@@ -138,6 +150,8 @@ public final class GracefulShutdownManager: @unchecked Sendable {
138150
fileprivate var handlerCounter: UInt64 = 0
139151
/// A boolean indicating if we have been shutdown already.
140152
fileprivate var isShuttingDown = false
153+
/// Continuations to resume after all of the handlers have been executed.
154+
fileprivate var gracefulShutdownFinishedContinuations = [CheckedContinuation<Void, Never>]()
141155
}
142156

143157
private let state = LockedValueBox(State())
@@ -191,6 +205,25 @@ public final class GracefulShutdownManager: @unchecked Sendable {
191205
}
192206

193207
state.handlers.removeAll()
208+
209+
for continuation in state.gracefulShutdownFinishedContinuations {
210+
continuation.resume()
211+
}
212+
213+
state.gracefulShutdownFinishedContinuations.removeAll()
214+
}
215+
}
216+
217+
func waitForGracefulShutdown() async {
218+
await withCheckedContinuation { continuation in
219+
self.state.withLockedValue { state in
220+
guard !state.isShuttingDown else {
221+
continuation.resume()
222+
return
223+
}
224+
225+
state.gracefulShutdownFinishedContinuations.append(continuation)
226+
}
194227
}
195228
}
196229
}

Tests/ServiceLifecycleTests/GracefulShutdownTests.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,4 +253,23 @@ final class GracefulShutdownTests: XCTestCase {
253253
XCTAssertTrue(Task.isShuttingDownGracefully)
254254
}
255255
}
256+
257+
func testWaitForGracefulShutdown() async throws {
258+
try await testGracefulShutdown { gracefulShutdownTestTrigger in
259+
try await withThrowingTaskGroup(of: Void.self) { group in
260+
group.addTask {
261+
try await Task.sleep(for: .milliseconds(10))
262+
gracefulShutdownTestTrigger.triggerGracefulShutdown()
263+
}
264+
265+
await withGracefulShutdownHandler {
266+
await waitForGracefulShutdown()
267+
} onGracefulShutdown: {
268+
// No-op
269+
}
270+
271+
try await group.waitForAll()
272+
}
273+
}
274+
}
256275
}

0 commit comments

Comments
 (0)