Skip to content

Commit b6834b3

Browse files
committed
Extend graceful shutdown APIs and tolerate running outside of a ServiceRunner
1 parent 7e412d6 commit b6834b3

File tree

3 files changed

+33
-11
lines changed

3 files changed

+33
-11
lines changed

Sources/ServiceLifecycle/GracefulShutdown.swift

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,27 +25,21 @@ import ConcurrencyHelpers
2525
/// A common use-case is to listen to graceful shutdown and use the `ServerQuiescingHelper` from `swift-nio-extras` to
2626
/// trigger the quiescing sequence. Furthermore, graceful shutdown will propagate to any child task that is currently executing
2727
///
28+
/// - Important: This method will only set up a handler if run inside ``ServiceRunner`` otherwise no graceful shutdown handler
29+
/// will be set up.
30+
///
2831
/// - Parameters:
29-
/// - requiresRunningInsideServiceRunner: Indicates if this method requires to be run from within a ``ServiceRunner`` child task.
30-
/// This defaults to `true` and if run outside of a ``ServiceRunner`` child task will `fatalError`. If set to `false` then
31-
/// no graceful shutdown handler will be setup if not called inside a ``ServiceRunner`` child task. This is useful for code that
32-
/// can run both inside and outside of``ServiceRunner`` child tasks.
3332
/// - operation: The actual operation.
3433
/// - handler: The handler which is invoked once graceful shutdown has been triggered.
3534
// Unsafely inheriting the executor is safe to do here since we are not calling any other async method
3635
// except the operation. This makes sure no other executor hops would occur here.
3736
@_unsafeInheritExecutor
3837
public func withGracefulShutdownHandler<T>(
39-
requiresRunningInsideServiceRunner: Bool = true,
4038
operation: () async throws -> T,
4139
onGracefulShutdown handler: @Sendable @escaping () -> Void
4240
) async rethrows -> T {
4341
guard let gracefulShutdownManager = TaskLocals.gracefulShutdownManager else {
44-
if !requiresRunningInsideServiceRunner {
45-
return try await operation()
46-
} else {
47-
fatalError("Trying to setup a graceful shutdown handler 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 setup a shutdown graceful handler outside the ServiceRunner.run method. Not setting up the handler.")
48-
}
42+
return try await operation()
4943
}
5044

5145
// We have to keep track of our handler here to remove it once the operation is finished.
@@ -59,6 +53,7 @@ public func withGracefulShutdownHandler<T>(
5953
return try await operation()
6054
}
6155

56+
/// This is just a helper type for the result of our task group.
6257
enum ValueOrGracefulShutdown<T> {
6358
case value(T)
6459
case gracefulShutdown
@@ -105,6 +100,19 @@ public func cancelOnGracefulShutdown<T>(_ operation: @Sendable @escaping () asyn
105100
}
106101
}
107102

103+
extension Task where Success == Never, Failure == Never {
104+
/// A Boolean value that indicates whether the task is gracefully shutting down
105+
///
106+
/// After the value of this property becomes `true`, it remains `true` indefinitely. There is no way to undo a graceful shutdown.
107+
public static var isShuttingDownGracefully: Bool {
108+
guard let gracefulShutdownManager = TaskLocals.gracefulShutdownManager else {
109+
return false
110+
}
111+
112+
return gracefulShutdownManager.isShuttingDown
113+
}
114+
}
115+
108116
@_spi(TestKit)
109117
public enum TaskLocals {
110118
@TaskLocal
@@ -132,6 +140,10 @@ public final class GracefulShutdownManager: @unchecked Sendable {
132140

133141
private let state = LockedValueBox(State())
134142

143+
var isShuttingDown: Bool {
144+
self.state.withLockedValue { return $0.isShuttingDown }
145+
}
146+
135147
@_spi(TestKit)
136148
public init() {}
137149

Tests/ServiceLifecycleTests/AsyncCancelOnGracefulShutdownSequenceTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ final class AsyncCancelOnGracefulShutdownSequenceTests: XCTestCase {
3939

4040
await XCTAsyncAssertEqual(await iterator.next(), 1)
4141

42-
await gracefulShutdownTrigger.triggerGracefulShutdown()
42+
gracefulShutdownTrigger.triggerGracefulShutdown()
4343

4444
await XCTAsyncAssertEqual(await iterator.next(), nil)
4545
}

Tests/ServiceLifecycleTests/GracefulShutdownTests.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,4 +232,14 @@ final class GracefulShutdownTests: XCTestCase {
232232
}
233233
}
234234
}
235+
236+
func testIsShuttingDownGracefully() async throws {
237+
await testGracefulShutdown { gracefulShutdownTestTrigger in
238+
XCTAssertFalse(Task.isShuttingDownGracefully)
239+
240+
gracefulShutdownTestTrigger.triggerGracefulShutdown()
241+
242+
XCTAssertTrue(Task.isShuttingDownGracefully)
243+
}
244+
}
235245
}

0 commit comments

Comments
 (0)