Skip to content

Commit f389916

Browse files
committed
add support for stateful handler
motivation: allow lifecycle handlers to have the library manage the state for them so they do not need to do that manually changes: * introduce LifecycleStartHandler and LifecycleShutdownHandler which can handle state on behalf of the lifecycle item * add registerStateful function to regsiter stateful handlers * add tests TODO: update docs
1 parent 272d1f5 commit f389916

File tree

4 files changed

+458
-14
lines changed

4 files changed

+458
-14
lines changed

Sources/Lifecycle/Lifecycle.swift

Lines changed: 149 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,25 +41,27 @@ extension LifecycleTask {
4141

4242
/// Supported startup and shutdown method styles
4343
public struct LifecycleHandler {
44+
@available(*, deprecated, renamed: "Handler")
4445
public typealias Callback = (@escaping (Error?) -> Void) -> Void
4546

46-
private let body: Callback?
47+
public typealias Handler = (@escaping (Error?) -> Void) -> Void
48+
49+
private let underlying: Handler?
4750

4851
/// Initialize a `LifecycleHandler` based on a completion handler.
4952
///
5053
/// - parameters:
51-
/// - callback: the underlying completion handler
52-
/// - noop: the underlying completion handler is a no-op
53-
public init(_ callback: Callback?) {
54-
self.body = callback
54+
/// - handler: the underlying completion handler
55+
public init(_ handler: Handler?) {
56+
self.underlying = handler
5557
}
5658

5759
/// Asynchronous `LifecycleHandler` based on a completion handler.
5860
///
5961
/// - parameters:
60-
/// - callback: the underlying completion handler
61-
public static func async(_ callback: @escaping Callback) -> LifecycleHandler {
62-
return LifecycleHandler(callback)
62+
/// - handler: the underlying async handler
63+
public static func async(_ handler: @escaping Handler) -> LifecycleHandler {
64+
return LifecycleHandler(handler)
6365
}
6466

6567
/// Asynchronous `LifecycleHandler` based on a blocking, throwing function.
@@ -82,15 +84,101 @@ public struct LifecycleHandler {
8284
return LifecycleHandler(nil)
8385
}
8486

85-
internal func run(_ callback: @escaping (Error?) -> Void) {
86-
let body = self.body ?? { callback in
87+
internal func run(_ completionHandler: @escaping (Error?) -> Void) {
88+
let body = self.underlying ?? { callback in
8789
callback(nil)
8890
}
89-
body(callback)
91+
body(completionHandler)
9092
}
9193

9294
internal var noop: Bool {
93-
return self.body == nil
95+
return self.underlying == nil
96+
}
97+
}
98+
99+
// MARK: - Stateful Lifecycle Handlers
100+
101+
/// LifecycleHandler for starting stateful tasks. The state can then be fed into a LifecycleShutdownHandler
102+
public struct LifecycleStartHandler<State> {
103+
public typealias Handler = (@escaping (Result<State, Error>) -> Void) -> Void
104+
105+
private let underlying: Handler
106+
107+
/// Initialize a `LifecycleHandler` based on a completion handler.
108+
///
109+
/// - parameters:
110+
/// - callback: the underlying completion handler
111+
public init(_ handler: @escaping Handler) {
112+
self.underlying = handler
113+
}
114+
115+
/// Asynchronous `LifecycleStartHandler` based on a completion handler.
116+
///
117+
/// - parameters:
118+
/// - handler: the underlying async handler
119+
public static func async(_ handler: @escaping Handler) -> LifecycleStartHandler {
120+
return LifecycleStartHandler(handler)
121+
}
122+
123+
/// Asynchronous `LifecycleStartHandler` based on a blocking, throwing function.
124+
///
125+
/// - parameters:
126+
/// - body: the underlying function
127+
public static func sync(_ body: @escaping () throws -> State) -> LifecycleStartHandler {
128+
return LifecycleStartHandler { completionHandler in
129+
do {
130+
let state = try body()
131+
completionHandler(.success(state))
132+
} catch {
133+
completionHandler(.failure(error))
134+
}
135+
}
136+
}
137+
138+
internal func run(_ completionHandler: @escaping (Result<State, Error>) -> Void) {
139+
self.underlying(completionHandler)
140+
}
141+
}
142+
143+
/// LifecycleHandler for shutting down stateful tasks. The state comes from a LifecycleStartHandler
144+
public struct LifecycleShutdownHandler<State> {
145+
public typealias Handler = (State, @escaping (Error?) -> Void) -> Void
146+
147+
private let underlying: Handler
148+
149+
/// Initialize a `LifecycleShutdownHandler` based on a completion handler.
150+
///
151+
/// - parameters:
152+
/// - handler: the underlying completion handler
153+
public init(_ handler: @escaping Handler) {
154+
self.underlying = handler
155+
}
156+
157+
/// Asynchronous `LifecycleShutdownHandler` based on a completion handler.
158+
///
159+
/// - parameters:
160+
/// - handler: the underlying async handler
161+
public static func async(_ handler: @escaping Handler) -> LifecycleShutdownHandler {
162+
return LifecycleShutdownHandler(handler)
163+
}
164+
165+
/// Asynchronous `LifecycleShutdownHandler` based on a blocking, throwing function.
166+
///
167+
/// - parameters:
168+
/// - body: the underlying function
169+
public static func sync(_ body: @escaping (State) throws -> Void) -> LifecycleShutdownHandler {
170+
return LifecycleShutdownHandler { state, completionHandler in
171+
do {
172+
try body(state)
173+
completionHandler(nil)
174+
} catch {
175+
completionHandler(error)
176+
}
177+
}
178+
}
179+
180+
internal func run(state: State, _ completionHandler: @escaping (Error?) -> Void) {
181+
self.underlying(state, completionHandler)
94182
}
95183
}
96184

@@ -516,6 +604,16 @@ extension LifecycleTasksContainer {
516604
public func registerShutdown(label: String, _ handler: LifecycleHandler) {
517605
self.register(label: label, start: .none, shutdown: handler)
518606
}
607+
608+
/// Adds a stateful `LifecycleTask` to a `LifecycleTasks` collection.
609+
///
610+
/// - parameters:
611+
/// - label: label of the item, useful for debugging.
612+
/// - start: `LifecycleStartHandler` to perform the startup and return the state.
613+
/// - shutdown: `LifecycleShutdownHandler` to perform the shutdown given the state.
614+
public func registerStateful<State>(label: String, start: LifecycleStartHandler<State>, shutdown: LifecycleShutdownHandler<State>) {
615+
self.register(StatefulLifecycleTask(label: label, start: start, shutdown: shutdown))
616+
}
519617
}
520618

521619
internal struct _LifecycleTask: LifecycleTask {
@@ -539,3 +637,42 @@ internal struct _LifecycleTask: LifecycleTask {
539637
self.shutdown.run(callback)
540638
}
541639
}
640+
641+
internal class StatefulLifecycleTask<State>: LifecycleTask {
642+
let label: String
643+
let shutdownIfNotStarted: Bool = false
644+
let start: LifecycleStartHandler<State>
645+
let shutdown: LifecycleShutdownHandler<State>
646+
647+
let stateLock = Lock()
648+
var state: State?
649+
650+
init(label: String, start: LifecycleStartHandler<State>, shutdown: LifecycleShutdownHandler<State>) {
651+
self.label = label
652+
self.start = start
653+
self.shutdown = shutdown
654+
}
655+
656+
func start(_ callback: @escaping (Error?) -> Void) {
657+
self.start.run { result in
658+
switch result {
659+
case .failure(let error):
660+
callback(error)
661+
case .success(let state):
662+
self.stateLock.withLock {
663+
self.state = state
664+
}
665+
callback(nil)
666+
}
667+
}
668+
}
669+
670+
func shutdown(_ callback: @escaping (Error?) -> Void) {
671+
guard let state = (self.stateLock.withLock { self.state }) else {
672+
return callback(UnknownState())
673+
}
674+
self.shutdown.run(state: state, callback)
675+
}
676+
677+
struct UnknownState: Error {}
678+
}

Sources/LifecycleNIOCompat/Bridge.swift

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import Lifecycle
1616
import NIO
1717

1818
extension LifecycleHandler {
19-
/// Asynchronous `Lifecycle.Handler` based on an `EventLoopFuture`.
19+
/// Asynchronous `LifecycleHandler` based on an `EventLoopFuture`.
2020
///
2121
/// - parameters:
2222
/// - future: function returning the underlying `EventLoopFuture`
@@ -32,8 +32,10 @@ extension LifecycleHandler {
3232
}
3333
}
3434
}
35+
}
3536

36-
/// `Lifecycle.Handler` that cancels a `RepeatedTask`.
37+
extension LifecycleHandler {
38+
/// `LifecycleHandler` that cancels a `RepeatedTask`.
3739
///
3840
/// - parameters:
3941
/// - task: `RepeatedTask` to be cancelled
@@ -47,6 +49,39 @@ extension LifecycleHandler {
4749
}
4850
}
4951

52+
extension LifecycleStartHandler {
53+
/// Asynchronous `LifecycleStartHandler` based on an `EventLoopFuture`.
54+
///
55+
/// - parameters:
56+
/// - future: function returning the underlying `EventLoopFuture`
57+
public static func eventLoopFuture(_ future: @escaping () -> EventLoopFuture<State>) -> LifecycleStartHandler {
58+
return LifecycleStartHandler { callback in
59+
future().whenComplete { result in
60+
callback(result)
61+
}
62+
}
63+
}
64+
}
65+
66+
extension LifecycleShutdownHandler {
67+
/// Asynchronous `LifecycleShutdownHandler` based on an `EventLoopFuture`.
68+
///
69+
/// - parameters:
70+
/// - future: function returning the underlying `EventLoopFuture`
71+
public static func eventLoopFuture(_ future: @escaping (State) -> EventLoopFuture<Void>) -> LifecycleShutdownHandler {
72+
return LifecycleShutdownHandler { state, callback in
73+
future(state).whenComplete { result in
74+
switch result {
75+
case .success:
76+
callback(nil)
77+
case .failure(let error):
78+
callback(error)
79+
}
80+
}
81+
}
82+
}
83+
}
84+
5085
extension ComponentLifecycle {
5186
/// Starts the provided `LifecycleItem` array.
5287
/// Startup is performed in the order of items provided.

Tests/LifecycleTests/ComponentLifecycleTests+XCTest.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,15 @@ extension ComponentLifecycleTests {
5757
("testNOOPHandlers", testNOOPHandlers),
5858
("testShutdownOnlyStarted", testShutdownOnlyStarted),
5959
("testShutdownWhenStartFailedIfAsked", testShutdownWhenStartFailedIfAsked),
60+
("testStatefulSync", testStatefulSync),
61+
("testStatefulSyncStartError", testStatefulSyncStartError),
62+
("testStatefulSyncShutdownError", testStatefulSyncShutdownError),
63+
("testStatefulAsync", testStatefulAsync),
64+
("testStatefulAsyncStartError", testStatefulAsyncStartError),
65+
("testStatefulAsyncShutdownError", testStatefulAsyncShutdownError),
66+
("testStatefulNIO", testStatefulNIO),
67+
("testStatefulNIOStartFailure", testStatefulNIOStartFailure),
68+
("testStatefulNIOShutdownFailure", testStatefulNIOShutdownFailure),
6069
]
6170
}
6271
}

0 commit comments

Comments
 (0)