@@ -21,21 +21,8 @@ import Backtrace
21
21
import Dispatch
22
22
import Logging
23
23
24
- /// `Lifecycle` provides a basic mechanism to cleanly startup and shutdown the application, freeing resources in order before exiting.
25
- public class Lifecycle {
26
- private let logger = Logger ( label: " \( Lifecycle . self) " )
27
- private let shutdownGroup = DispatchGroup ( )
28
-
29
- private var state = State . idle
30
- private let stateSemaphore = DispatchSemaphore ( value: 1 )
31
-
32
- private var items = [ LifecycleItem] ( )
33
- private let itemsSemaphore = DispatchSemaphore ( value: 1 )
34
-
35
- /// Creates a `Lifecycle` instance.
36
- public init ( ) {
37
- self . shutdownGroup. enter ( )
38
- }
24
+ public struct TopLevelLifecycle {
25
+ private let lifecycle = Lifecycle ( label: " \( TopLevelLifecycle . self) " )
39
26
40
27
/// Starts the provided `LifecycleItem` array and waits (blocking) until a shutdown `Signal` is captured or `Lifecycle.shutdown` is called on another thread.
41
28
/// Startup is performed in the order of items provided.
@@ -45,14 +32,13 @@ public class Lifecycle {
45
32
public func startAndWait( configuration: Configuration = . init( ) ) throws {
46
33
let waitSemaphore = DispatchSemaphore ( value: 0 )
47
34
var startError : Error ?
48
- let items = self . itemsSemaphore. lock { self . items }
49
- self . _start ( configuration: configuration, items: items) { error in
35
+ self . start ( configuration: configuration) { error in
50
36
startError = error
51
37
waitSemaphore. signal ( )
52
38
}
53
39
waitSemaphore. wait ( )
54
40
try startError. map { throw $0 }
55
- self . shutdownGroup . wait ( )
41
+ self . wait ( )
56
42
}
57
43
58
44
/// Starts the provided `LifecycleItem` array.
@@ -62,13 +48,151 @@ public class Lifecycle {
62
48
/// - configuration: Defines lifecycle `Configuration`
63
49
/// - callback: The handler which is called after the start operation completes. The parameter will be `nil` on success and contain the `Error` otherwise.
64
50
public func start( configuration: Configuration = . init( ) , callback: @escaping ( Error ? ) -> Void ) {
51
+ if configuration. installBacktrace {
52
+ self . lifecycle. logger. info ( " installing backtrace " )
53
+ Backtrace . install ( )
54
+ }
55
+ configuration. shutdownSignal? . forEach { signal in
56
+ self . lifecycle. logger. info ( " setting up shutdown hook on \( signal) " )
57
+ let signalSource = TopLevelLifecycle . trap ( signal: signal, handler: { signal in
58
+ self . lifecycle. logger. info ( " intercepted signal: \( signal) " )
59
+ self . shutdown ( on: configuration. callbackQueue)
60
+ } )
61
+ self . lifecycle. shutdownGroup. notify ( queue: DispatchQueue . global ( ) ) {
62
+ signalSource. cancel ( )
63
+ }
64
+ }
65
+ self . lifecycle. start ( on: configuration. callbackQueue) { error in
66
+ callback ( error)
67
+ }
68
+ }
69
+
70
+ /// Shuts down the `LifecycleItem` array provided in `start` or `startAndWait`.
71
+ /// Shutdown is performed in reverse order of items provided./
72
+ ///
73
+ /// - parameters:
74
+ /// - on: `DispatchQueue` to run the handler on
75
+ /// - callback: The handler which is called after the start operation completes. The parameter will be `nil` on success and contain the `Error` otherwise.
76
+ public func shutdown( on queue: DispatchQueue = DispatchQueue . global ( ) , callback: @escaping ( Error ? ) -> Void = { _ in } ) {
77
+ self . lifecycle. shutdown ( on: queue, callback: callback)
78
+ }
79
+
80
+ /// Waits (blocking) until shutdown signal is captured or `Lifecycle.shutdown` is invoked on another thread.
81
+ public func wait( ) {
82
+ self . lifecycle. wait ( )
83
+ }
84
+
85
+ /// Adds a `LifecycleItem` to a `LifecycleItems` collection.
86
+ ///
87
+ /// - parameters:
88
+ /// - items: one or more `LifecycleItem`.
89
+ func register( _ items: [ LifecycleItem ] ) {
90
+ self . lifecycle. register ( items)
91
+ }
92
+
93
+ /// Adds a `LifecycleItem` to a `LifecycleItems` collection.
94
+ ///
95
+ /// - parameters:
96
+ /// - items: one or more `LifecycleItem`.
97
+ internal func register( _ items: LifecycleItem ... ) {
98
+ self . lifecycle. register ( items)
99
+ }
100
+
101
+ /// Add a `LifecycleItem` to a `LifecycleItems` collection.
102
+ ///
103
+ /// - parameters:
104
+ /// - label: label of the item, useful for debugging.
105
+ /// - start: closure to perform the startup.
106
+ /// - shutdown: closure to perform the shutdown.
107
+ func register( label: String , start: Lifecycle . Handler , shutdown: Lifecycle . Handler ) {
108
+ self . lifecycle. register ( label: label, start: start, shutdown: shutdown)
109
+ }
110
+
111
+ /// Adds a `LifecycleItem` to a `LifecycleItems` collection.
112
+ ///
113
+ /// - parameters:
114
+ /// - label: label of the item, useful for debugging.
115
+ /// - shutdown: closure to perform the shutdown.
116
+ func registerShutdown( label: String , _ handler: Lifecycle . Handler ) {
117
+ self . lifecycle. registerShutdown ( label: label, handler)
118
+ }
119
+
120
+ /// Adds a `LifecycleItem` to a `LifecycleItems` collection.
121
+ ///
122
+ /// - parameters:
123
+ /// - label: label of the item, useful for debugging.
124
+ /// - start: closure to perform the shutdown.
125
+ /// - shutdown: closure to perform the shutdown.
126
+ func register( label: String , start: @escaping ( ) throws -> Void , shutdown: @escaping ( ) throws -> Void ) {
127
+ self . lifecycle. register ( label: label, start: start, shutdown: shutdown)
128
+ }
129
+
130
+ /// Adds a `LifecycleItem` to a `LifecycleItems` collection.
131
+ ///
132
+ /// - parameters:
133
+ /// - label: label of the item, useful for debugging.
134
+ /// - shutdown: closure to perform the shutdown.
135
+ func registerShutdown( label: String , _ handler: @escaping ( ) throws -> Void ) {
136
+ self . lifecycle. registerShutdown ( label: label, handler)
137
+ }
138
+ }
139
+
140
+ /// `Lifecycle` provides a basic mechanism to cleanly startup and shutdown the application, freeing resources in order before exiting.
141
+ public class Lifecycle : LifecycleItem {
142
+ public let label : String
143
+ internal let logger : Logger
144
+
145
+ private var state = State . idle
146
+ private let stateSemaphore = DispatchSemaphore ( value: 1 )
147
+
148
+ private var items = [ LifecycleItem] ( )
149
+ private let itemsSemaphore = DispatchSemaphore ( value: 1 )
150
+
151
+ internal let shutdownGroup = DispatchGroup ( )
152
+
153
+ /// Creates a `Lifecycle` instance.
154
+ public init ( label: String ) {
155
+ self . label = label
156
+ self . logger = Logger ( label: " \( Lifecycle . self) \( label) " )
157
+ self . shutdownGroup. enter ( )
158
+ }
159
+
160
+ /// Starts the provided `LifecycleItem` array.
161
+ /// Startup is performed in the order of items provided.
162
+ ///
163
+ /// - parameters:
164
+ /// - callback: The handler which is called after the start operation completes. The parameter will be `nil` on success and contain the `Error` otherwise.
165
+ public func start( callback: @escaping ( Error ? ) -> Void ) {
166
+ self . start ( on: DispatchQueue . global ( ) , callback: callback)
167
+ }
168
+
169
+ /// Starts the provided `LifecycleItem` array.
170
+ /// Startup is performed in the order of items provided.
171
+ ///
172
+ /// - parameters:
173
+ /// - on: `DispatchQueue` to run the handler on
174
+ /// - callback: The handler which is called after the start operation completes. The parameter will be `nil` on success and contain the `Error` otherwise.
175
+ public func start( on queue: DispatchQueue , callback: @escaping ( Error ? ) -> Void ) {
65
176
let items = self . itemsSemaphore. lock { self . items }
66
- self . _start ( configuration: configuration, items: items, callback: callback)
177
+ self . _start ( on: queue, items: items, callback: callback)
178
+ }
179
+
180
+ /// Shuts down the `LifecycleItem` array provided in `start` or `startAndWait`.
181
+ /// Shutdown is performed in reverse order of items provided.
182
+ ///
183
+ /// - parameters:
184
+ /// - callback: The handler which is called after the start operation completes. The parameter will be `nil` on success and contain the `Error` otherwise.
185
+ public func shutdown( callback: @escaping ( Error ? ) -> Void = { _ in } ) {
186
+ self . shutdown ( on: DispatchQueue . global ( ) , callback: callback)
67
187
}
68
188
69
189
/// Shuts down the `LifecycleItem` array provided in `start` or `startAndWait`.
70
190
/// Shutdown is performed in reverse order of items provided.
71
- public func shutdown( on queue: DispatchQueue = DispatchQueue . global ( ) , callback: @escaping ( [ String : Error ] ) -> Void = { _ in } ) {
191
+ ///
192
+ /// - parameters:
193
+ /// - on: `DispatchQueue` to run the handler on
194
+ /// - callback: The handler which is called after the start operation completes. The parameter will be `nil` on success and contain the `Error` otherwise.
195
+ public func shutdown( on queue: DispatchQueue , callback: @escaping ( Error ? ) -> Void = { _ in } ) {
72
196
self . stateSemaphore. wait ( )
73
197
switch self . state {
74
198
case . idle:
@@ -85,32 +209,28 @@ public class Lifecycle {
85
209
self . stateSemaphore. signal ( )
86
210
self . _shutdown ( on: queue, items: items) { errors in
87
211
defer { self . shutdownGroup. leave ( ) }
88
- callback ( errors)
212
+ callback ( errors. count > 0 ? ShutdownError ( errors : errors ) : nil )
89
213
}
90
214
}
91
215
}
92
216
93
- /// Waits (blocking) until shutdown signal is captured or `Lifecycle.shutdown` is invoked on another thread.
94
217
public func wait( ) {
95
218
self . shutdownGroup. wait ( )
96
219
}
97
220
98
221
// MARK: - private
99
222
100
- private func _start( configuration : Configuration , items: [ LifecycleItem ] , callback: @escaping ( Error ? ) -> Void ) {
223
+ private func _start( on queue : DispatchQueue = DispatchQueue . global ( ) , items: [ LifecycleItem ] , callback: @escaping ( Error ? ) -> Void ) {
101
224
self . stateSemaphore. lock {
102
225
guard case . idle = self . state else {
103
226
preconditionFailure ( " invalid state, \( self . state) " )
104
227
}
105
228
precondition ( items. count > 0 , " invalid number of items, must be > 0 " )
106
229
self . logger. info ( " starting lifecycle " )
107
- if configuration. installBacktrace {
108
- self . logger. info ( " installing backtrace " )
109
- Backtrace . install ( )
110
- }
230
+
111
231
self . state = . starting
112
232
}
113
- self . _start ( on: configuration . callbackQueue , items: items, index: 0 ) { _, error in
233
+ self . _start ( on: queue , items: items, index: 0 ) { _, error in
114
234
self . stateSemaphore. wait ( )
115
235
if error != nil {
116
236
self . state = . shuttingDown
@@ -119,23 +239,13 @@ public class Lifecycle {
119
239
case . shuttingDown:
120
240
self . stateSemaphore. signal ( )
121
241
// shutdown was called while starting, or start failed, shutdown what we can
122
- self . _shutdown ( on: configuration . callbackQueue , items: items) { _ in
242
+ self . _shutdown ( on: queue , items: items) { _ in
123
243
callback ( error)
124
244
self . shutdownGroup. leave ( )
125
245
}
126
246
case . starting:
127
247
self . state = . started( items)
128
248
self . stateSemaphore. signal ( )
129
- configuration. shutdownSignal? . forEach { signal in
130
- self . logger. info ( " setting up shutdown hook on \( signal) " )
131
- let signalSource = Lifecycle . trap ( signal: signal, handler: { signal in
132
- self . logger. info ( " intercepted signal: \( signal) " )
133
- self . shutdown ( on: configuration. callbackQueue)
134
- } )
135
- self . shutdownGroup. notify ( queue: DispatchQueue . global ( ) ) {
136
- signalSource. cancel ( )
137
- }
138
- }
139
249
return callback ( nil )
140
250
default :
141
251
preconditionFailure ( " invalid state, \( self . state) " )
@@ -154,7 +264,7 @@ public class Lifecycle {
154
264
self . logger. info ( " starting item [ \( items [ index] . label) ] " )
155
265
start { error in
156
266
if let error = error {
157
- self . logger. info ( " failed to start [ \( items [ index] . label) ]: \( error) " )
267
+ self . logger. error ( " failed to start [ \( items [ index] . label) ]: \( error) " )
158
268
return callback ( index, error)
159
269
}
160
270
// shutdown called while starting
@@ -211,6 +321,10 @@ public class Lifecycle {
211
321
case shuttingDown
212
322
case shutdown
213
323
}
324
+
325
+ public struct ShutdownError : Error {
326
+ let errors : [ String : Error ]
327
+ }
214
328
}
215
329
216
330
extension Lifecycle {
@@ -220,31 +334,11 @@ extension Lifecycle {
220
334
let shutdown : Handler
221
335
222
336
func start( callback: @escaping ( Error ? ) -> Void ) {
223
- self . start. run ( callback)
337
+ self . start. run ( callback: callback )
224
338
}
225
339
226
340
func shutdown( callback: @escaping ( Error ? ) -> Void ) {
227
- self . shutdown. run ( callback)
228
- }
229
- }
230
- }
231
-
232
- extension Lifecycle {
233
- /// `Lifecycle` configuration options.
234
- public struct Configuration {
235
- /// Defines the `DispatchQueue` on which startup and shutdown handlers are executed.
236
- public let callbackQueue : DispatchQueue
237
- /// Defines if to install a crash signal trap that prints backtraces.
238
- public let shutdownSignal : [ Signal ] ?
239
- /// Defines what, if any, signals to trap for invoking shutdown.
240
- public let installBacktrace : Bool
241
-
242
- public init ( callbackQueue: DispatchQueue = DispatchQueue . global ( ) ,
243
- shutdownSignal: [ Signal ] ? = [ . TERM, . INT] ,
244
- installBacktrace: Bool = true ) {
245
- self . callbackQueue = callbackQueue
246
- self . shutdownSignal = shutdownSignal
247
- self . installBacktrace = installBacktrace
341
+ self . shutdown. run ( callback: callback)
248
342
}
249
343
}
250
344
}
@@ -322,7 +416,7 @@ public extension Lifecycle {
322
416
///
323
417
/// - parameters:
324
418
/// - callback: the underlying completion handler
325
- public init ( _ callback: @escaping ( @escaping ( Error ? ) -> Void ) -> Void ) {
419
+ public init ( callback: @escaping ( @escaping ( Error ? ) -> Void ) -> Void ) {
326
420
self . body = callback
327
421
}
328
422
@@ -331,20 +425,20 @@ public extension Lifecycle {
331
425
/// - parameters:
332
426
/// - callback: the underlying completion handler
333
427
public static func async ( _ callback: @escaping ( @escaping ( Error ? ) -> Void ) -> Void ) -> Handler {
334
- return Handler ( callback)
428
+ return Handler ( callback: callback )
335
429
}
336
430
337
431
/// Asynchronous `Lifecycle.Handler` based on a blocking, throwing function.
338
432
///
339
433
/// - parameters:
340
434
/// - body: the underlying function
341
435
public static func sync( _ body: @escaping ( ) throws -> Void ) -> Handler {
342
- return Handler { completionHandler in
436
+ return Handler { callback in
343
437
do {
344
438
try body ( )
345
- completionHandler ( nil )
439
+ callback ( nil )
346
440
} catch {
347
- completionHandler ( error)
441
+ callback ( error)
348
442
}
349
443
}
350
444
}
@@ -356,7 +450,7 @@ public extension Lifecycle {
356
450
}
357
451
}
358
452
359
- internal func run( _ callback: @escaping ( Error ? ) -> Void ) {
453
+ internal func run( callback: @escaping ( Error ? ) -> Void ) {
360
454
self . body ( callback)
361
455
}
362
456
}
@@ -369,7 +463,27 @@ public protocol LifecycleItem {
369
463
func shutdown( callback: @escaping ( Error ? ) -> Void )
370
464
}
371
465
372
- extension Lifecycle {
466
+ extension TopLevelLifecycle {
467
+ /// `Lifecycle` configuration options.
468
+ public struct Configuration {
469
+ /// Defines the `DispatchQueue` on which startup and shutdown handlers are executed.
470
+ public let callbackQueue : DispatchQueue
471
+ /// Defines if to install a crash signal trap that prints backtraces.
472
+ public let shutdownSignal : [ Signal ] ?
473
+ /// Defines what, if any, signals to trap for invoking shutdown.
474
+ public let installBacktrace : Bool
475
+
476
+ public init ( callbackQueue: DispatchQueue = DispatchQueue . global ( ) ,
477
+ shutdownSignal: [ Signal ] ? = [ . TERM, . INT] ,
478
+ installBacktrace: Bool = true ) {
479
+ self . callbackQueue = callbackQueue
480
+ self . shutdownSignal = shutdownSignal
481
+ self . installBacktrace = installBacktrace
482
+ }
483
+ }
484
+ }
485
+
486
+ extension TopLevelLifecycle {
373
487
/// Setup a signal trap.
374
488
///
375
489
/// - parameters:
0 commit comments