@@ -30,12 +30,22 @@ public protocol LifecycleTask {
30
30
var shutdownIfNotStarted : Bool { get }
31
31
func start( _ callback: @escaping ( Error ? ) -> Void )
32
32
func shutdown( _ callback: @escaping ( Error ? ) -> Void )
33
+ var logStart : Bool { get }
34
+ var logShutdown : Bool { get }
33
35
}
34
36
35
- extension LifecycleTask {
36
- public var shutdownIfNotStarted : Bool {
37
+ public extension LifecycleTask {
38
+ var shutdownIfNotStarted : Bool {
37
39
return false
38
40
}
41
+
42
+ var logStart : Bool {
43
+ return true
44
+ }
45
+
46
+ var logShutdown : Bool {
47
+ return true
48
+ }
39
49
}
40
50
41
51
// MARK: - LifecycleHandler
@@ -97,8 +107,8 @@ public struct LifecycleHandler {
97
107
98
108
#if canImport(_Concurrency) && compiler(>=5.5.2)
99
109
@available ( macOS 10 . 15 , iOS 13 , tvOS 13 , watchOS 6 , * )
100
- extension LifecycleHandler {
101
- public init ( _ handler: @escaping ( ) async throws -> Void ) {
110
+ public extension LifecycleHandler {
111
+ init ( _ handler: @escaping ( ) async throws -> Void ) {
102
112
self = LifecycleHandler { callback in
103
113
Task {
104
114
do {
@@ -111,7 +121,7 @@ extension LifecycleHandler {
111
121
}
112
122
}
113
123
114
- public static func async ( _ handler: @escaping ( ) async throws -> Void ) -> LifecycleHandler {
124
+ static func async ( _ handler: @escaping ( ) async throws -> Void ) -> LifecycleHandler {
115
125
return LifecycleHandler ( handler)
116
126
}
117
127
}
@@ -161,8 +171,8 @@ public struct LifecycleStartHandler<State> {
161
171
162
172
#if canImport(_Concurrency) && compiler(>=5.5.2)
163
173
@available ( macOS 10 . 15 , iOS 13 , tvOS 13 , watchOS 6 , * )
164
- extension LifecycleStartHandler {
165
- public init ( _ handler: @escaping ( ) async throws -> State ) {
174
+ public extension LifecycleStartHandler {
175
+ init ( _ handler: @escaping ( ) async throws -> State ) {
166
176
self = LifecycleStartHandler { callback in
167
177
Task {
168
178
do {
@@ -175,7 +185,7 @@ extension LifecycleStartHandler {
175
185
}
176
186
}
177
187
178
- public static func async ( _ handler: @escaping ( ) async throws -> State ) -> LifecycleStartHandler {
188
+ static func async ( _ handler: @escaping ( ) async throws -> State ) -> LifecycleStartHandler {
179
189
return LifecycleStartHandler ( handler)
180
190
}
181
191
}
@@ -223,8 +233,8 @@ public struct LifecycleShutdownHandler<State> {
223
233
224
234
#if canImport(_Concurrency) && compiler(>=5.5.2)
225
235
@available ( macOS 10 . 15 , iOS 13 , tvOS 13 , watchOS 6 , * )
226
- extension LifecycleShutdownHandler {
227
- public init ( _ handler: @escaping ( State ) async throws -> Void ) {
236
+ public extension LifecycleShutdownHandler {
237
+ init ( _ handler: @escaping ( State ) async throws -> Void ) {
228
238
self = LifecycleShutdownHandler { state, callback in
229
239
Task {
230
240
do {
@@ -237,7 +247,7 @@ extension LifecycleShutdownHandler {
237
247
}
238
248
}
239
249
240
- public static func async ( _ handler: @escaping ( State ) async throws -> Void ) -> LifecycleShutdownHandler {
250
+ static func async ( _ handler: @escaping ( State ) async throws -> Void ) -> LifecycleShutdownHandler {
241
251
return LifecycleShutdownHandler ( handler)
242
252
}
243
253
}
@@ -317,9 +327,14 @@ public struct ServiceLifecycle {
317
327
self . log ( " intercepted signal: \( signal) " )
318
328
self . shutdown ( )
319
329
} , cancelAfterTrap: true )
320
- self . underlying. shutdownGroup. notify ( queue: . global( ) ) {
321
- signalSource. cancel ( )
322
- }
330
+ // register cleanup as the last task
331
+ self . registerShutdown ( label: " \( signal) shutdown hook cleanup " , . sync {
332
+ // cancel if not already canceled by the trap
333
+ if !signalSource. isCancelled {
334
+ signalSource. cancel ( )
335
+ ServiceLifecycle . removeTrap ( signal: signal)
336
+ }
337
+ } )
323
338
}
324
339
}
325
340
@@ -328,7 +343,7 @@ public struct ServiceLifecycle {
328
343
}
329
344
}
330
345
331
- extension ServiceLifecycle {
346
+ public extension ServiceLifecycle {
332
347
private static var trapped : Set < Int32 > = [ ]
333
348
private static let trappedLock = Lock ( )
334
349
@@ -340,27 +355,39 @@ extension ServiceLifecycle {
340
355
/// - on: DispatchQueue to run the signal handler on (default global dispatch queue)
341
356
/// - cancelAfterTrap: Defaults to false, which means the signal handler can be run multiple times. If true, the DispatchSignalSource will be cancelled after being trapped once.
342
357
/// - returns: a `DispatchSourceSignal` for the given trap. The source must be cancelled by the caller.
343
- public static func trap( signal sig: Signal , handler: @escaping ( Signal ) -> Void , on queue: DispatchQueue = . global( ) , cancelAfterTrap: Bool = false ) -> DispatchSourceSignal {
358
+ static func trap( signal sig: Signal , handler: @escaping ( Signal ) -> Void , on queue: DispatchQueue = . global( ) , cancelAfterTrap: Bool = false ) -> DispatchSourceSignal {
344
359
// on linux, we can call singal() once per process
345
360
self . trappedLock. withLockVoid {
346
- if !trapped. contains ( sig. rawValue) {
361
+ if !self . trapped. contains ( sig. rawValue) {
347
362
signal ( sig. rawValue, SIG_IGN)
348
- trapped. insert ( sig. rawValue)
363
+ self . trapped. insert ( sig. rawValue)
349
364
}
350
365
}
351
366
let signalSource = DispatchSource . makeSignalSource ( signal: sig. rawValue, queue: queue)
352
367
signalSource. setEventHandler {
368
+ // run handler first
369
+ handler ( sig)
370
+ // then cancel trap if so requested
353
371
if cancelAfterTrap {
354
372
signalSource. cancel ( )
373
+ self . removeTrap ( signal: sig)
355
374
}
356
- handler ( sig)
357
375
}
358
376
signalSource. resume ( )
359
377
return signalSource
360
378
}
361
379
380
+ static func removeTrap( signal sig: Signal ) {
381
+ self . trappedLock. withLockVoid {
382
+ if self . trapped. contains ( sig. rawValue) {
383
+ signal ( sig. rawValue, SIG_DFL)
384
+ self . trapped. remove ( sig. rawValue)
385
+ }
386
+ }
387
+ }
388
+
362
389
/// A system signal
363
- public struct Signal : Equatable , CustomStringConvertible {
390
+ struct Signal : Equatable , CustomStringConvertible {
364
391
internal var rawValue : CInt
365
392
366
393
public static let TERM = Signal ( rawValue: SIGTERM)
@@ -395,9 +422,9 @@ extension ServiceLifecycle: LifecycleTasksContainer {
395
422
}
396
423
}
397
424
398
- extension ServiceLifecycle {
425
+ public extension ServiceLifecycle {
399
426
/// `ServiceLifecycle` configuration options.
400
- public struct Configuration {
427
+ struct Configuration {
401
428
/// Defines the `label` for the lifeycle and its Logger
402
429
public var label : String
403
430
/// Defines the `Logger` to log with.
@@ -413,7 +440,8 @@ extension ServiceLifecycle {
413
440
logger: Logger ? = nil ,
414
441
callbackQueue: DispatchQueue = . global( ) ,
415
442
shutdownSignal: [ Signal ] ? = [ . TERM, . INT] ,
416
- installBacktrace: Bool = true ) {
443
+ installBacktrace: Bool = true )
444
+ {
417
445
self . label = label
418
446
self . logger = logger ?? Logger ( label: label)
419
447
self . callbackQueue = callbackQueue
@@ -433,7 +461,7 @@ struct ShutdownError: Error {
433
461
public class ComponentLifecycle : LifecycleTask {
434
462
public let label : String
435
463
fileprivate let logger : Logger
436
- internal let shutdownGroup = DispatchGroup ( )
464
+ fileprivate let shutdownGroup = DispatchGroup ( )
437
465
438
466
private var state = State . idle ( [ ] )
439
467
private let stateLock = Lock ( )
@@ -596,13 +624,15 @@ public class ComponentLifecycle: LifecycleTask {
596
624
597
625
private func startTask( on queue: DispatchQueue , tasks: [ LifecycleTask ] , index: Int , callback: @escaping ( [ LifecycleTask ] , Error ? ) -> Void ) {
598
626
// async barrier
599
- let start = { ( callback) -> Void in queue. async { tasks [ index] . start ( callback) } }
600
- let callback = { ( index, error) -> Void in queue. async { callback ( index, error) } }
627
+ let start = { callback in queue. async { tasks [ index] . start ( callback) } }
628
+ let callback = { index, error in queue. async { callback ( index, error) } }
601
629
602
630
if index >= tasks. count {
603
631
return callback ( tasks, nil )
604
632
}
605
- self . logger. info ( " starting tasks [ \( tasks [ index] . label) ] " )
633
+ if tasks [ index] . logStart {
634
+ self . logger. info ( " starting tasks [ \( tasks [ index] . label) ] " )
635
+ }
606
636
let startTime = DispatchTime . now ( )
607
637
start { error in
608
638
Timer ( label: " \( self . label) . \( tasks [ index] . label) .lifecycle.start " ) . recordNanoseconds ( DispatchTime . now ( ) . uptimeNanoseconds - startTime. uptimeNanoseconds)
@@ -642,14 +672,16 @@ public class ComponentLifecycle: LifecycleTask {
642
672
643
673
private func shutdownTask( on queue: DispatchQueue , tasks: [ LifecycleTask ] , index: Int , errors: [ String : Error ] ? , callback: @escaping ( [ String : Error ] ? ) -> Void ) {
644
674
// async barrier
645
- let shutdown = { ( callback) -> Void in queue. async { tasks [ index] . shutdown ( callback) } }
646
- let callback = { ( errors) -> Void in queue. async { callback ( errors) } }
675
+ let shutdown = { callback in queue. async { tasks [ index] . shutdown ( callback) } }
676
+ let callback = { errors in queue. async { callback ( errors) } }
647
677
648
678
if index >= tasks. count {
649
679
return callback ( errors)
650
680
}
651
681
652
- self . logger. info ( " stopping tasks [ \( tasks [ index] . label) ] " )
682
+ if tasks [ index] . logShutdown {
683
+ self . logger. info ( " stopping tasks [ \( tasks [ index] . label) ] " )
684
+ }
653
685
let startTime = DispatchTime . now ( )
654
686
shutdown { error in
655
687
Timer ( label: " \( self . label) . \( tasks [ index] . label) .lifecycle.shutdown " ) . recordNanoseconds ( DispatchTime . now ( ) . uptimeNanoseconds - startTime. uptimeNanoseconds)
@@ -694,12 +726,12 @@ public protocol LifecycleTasksContainer {
694
726
func register( _ tasks: [ LifecycleTask ] )
695
727
}
696
728
697
- extension LifecycleTasksContainer {
729
+ public extension LifecycleTasksContainer {
698
730
/// Adds a `LifecycleTask` to a `LifecycleTasks` collection.
699
731
///
700
732
/// - parameters:
701
733
/// - tasks: one or more `LifecycleTask`.
702
- public func register( _ tasks: LifecycleTask ... ) {
734
+ func register( _ tasks: LifecycleTask ... ) {
703
735
self . register ( tasks)
704
736
}
705
737
@@ -709,7 +741,7 @@ extension LifecycleTasksContainer {
709
741
/// - label: label of the item, useful for debugging.
710
742
/// - start: `Handler` to perform the startup.
711
743
/// - shutdown: `Handler` to perform the shutdown.
712
- public func register( label: String , start: LifecycleHandler , shutdown: LifecycleHandler , shutdownIfNotStarted: Bool ? = nil ) {
744
+ func register( label: String , start: LifecycleHandler , shutdown: LifecycleHandler , shutdownIfNotStarted: Bool ? = nil ) {
713
745
self . register ( _LifecycleTask ( label: label, shutdownIfNotStarted: shutdownIfNotStarted, start: start, shutdown: shutdown) )
714
746
}
715
747
@@ -718,7 +750,7 @@ extension LifecycleTasksContainer {
718
750
/// - parameters:
719
751
/// - label: label of the item, useful for debugging.
720
752
/// - handler: `Handler` to perform the shutdown.
721
- public func registerShutdown( label: String , _ handler: LifecycleHandler ) {
753
+ func registerShutdown( label: String , _ handler: LifecycleHandler ) {
722
754
self . register ( label: label, start: . none, shutdown: handler)
723
755
}
724
756
@@ -728,7 +760,7 @@ extension LifecycleTasksContainer {
728
760
/// - label: label of the item, useful for debugging.
729
761
/// - start: `LifecycleStartHandler` to perform the startup and return the state.
730
762
/// - shutdown: `LifecycleShutdownHandler` to perform the shutdown given the state.
731
- public func registerStateful< State> ( label: String , start: LifecycleStartHandler < State > , shutdown: LifecycleShutdownHandler < State > ) {
763
+ func registerStateful< State> ( label: String , start: LifecycleStartHandler < State > , shutdown: LifecycleShutdownHandler < State > ) {
732
764
self . register ( StatefulLifecycleTask ( label: label, start: start, shutdown: shutdown) )
733
765
}
734
766
}
@@ -739,12 +771,16 @@ internal struct _LifecycleTask: LifecycleTask {
739
771
let shutdownIfNotStarted : Bool
740
772
let start : LifecycleHandler
741
773
let shutdown : LifecycleHandler
774
+ let logStart : Bool
775
+ let logShutdown : Bool
742
776
743
777
init ( label: String , shutdownIfNotStarted: Bool ? = nil , start: LifecycleHandler , shutdown: LifecycleHandler ) {
744
778
self . label = label
745
779
self . shutdownIfNotStarted = shutdownIfNotStarted ?? start. noop
746
780
self . start = start
747
781
self . shutdown = shutdown
782
+ self . logStart = !start. noop
783
+ self . logShutdown = !shutdown. noop
748
784
}
749
785
750
786
func start( _ callback: @escaping ( Error ? ) -> Void ) {
0 commit comments