@@ -29,49 +29,49 @@ public enum Lambda {
29
29
///
30
30
/// - note: This is a blocking operation that will run forever, as it's lifecycle is managed by the AWS Lambda Runtime Engine.
31
31
public static func run( _ closure: @escaping LambdaClosure ) {
32
- let _ : LambdaLifecycleResult = self . run ( closure)
32
+ self . run ( closure : closure)
33
33
}
34
34
35
35
/// Run a Lambda defined by implementing the `LambdaHandler` protocol.
36
36
///
37
37
/// - note: This is a blocking operation that will run forever, as it's lifecycle is managed by the AWS Lambda Runtime Engine.
38
38
public static func run( _ handler: LambdaHandler ) {
39
- let _ : LambdaLifecycleResult = self . run ( handler: handler)
39
+ self . run ( handler: handler)
40
40
}
41
41
42
- // for testing
43
- internal static func run( maxTimes: Int = 0 , stopSignal: Signal = . TERM, _ closure: @escaping LambdaClosure ) -> LambdaLifecycleResult {
42
+ // for testing and internal use
43
+ @discardableResult
44
+ internal static func run( maxTimes: Int = 0 , stopSignal: Signal = . TERM, closure: @escaping LambdaClosure ) -> LambdaLifecycleResult {
44
45
return self . run ( handler: LambdaClosureWrapper ( closure) , maxTimes: maxTimes, stopSignal: stopSignal)
45
46
}
46
47
47
- // for testing
48
+ // for testing and internal use
49
+ @discardableResult
48
50
internal static func run( handler: LambdaHandler , maxTimes: Int = 0 , stopSignal: Signal = . TERM) -> LambdaLifecycleResult {
49
51
do {
50
- return try self . runAsync ( handler: handler, maxTimes: maxTimes, stopSignal: stopSignal) . wait ( )
52
+ return try self . runAsync ( handler: handler, maxTimes: maxTimes, stopSignal: stopSignal) . map { . success ( $0 ) } . wait ( )
51
53
} catch {
52
54
return . failure( error)
53
55
}
54
56
}
55
57
56
- internal static func runAsync( handler: LambdaHandler , maxTimes: Int = 0 , stopSignal: Signal = . TERM) -> EventLoopFuture < LambdaLifecycleResult > {
58
+ internal static func runAsync( handler: LambdaHandler , maxTimes: Int = 0 , stopSignal: Signal = . TERM) -> EventLoopFuture < Int > {
59
+ Backtrace . install ( )
57
60
let logger = Logger ( label: " Lambda " )
58
61
let lifecycle = Lifecycle ( logger: logger, handler: handler, maxTimes: maxTimes)
59
- Backtrace . install ( )
60
62
let signalSource = trap ( signal: stopSignal) { signal in
61
63
logger. info ( " intercepted signal: \( signal) " )
62
64
lifecycle. stop ( )
63
65
}
64
- let future = lifecycle. start ( )
65
- future. whenComplete { _ in
66
+ return lifecycle. start ( ) . always { _ in
66
67
lifecycle. stop ( )
67
68
signalSource. cancel ( )
68
69
}
69
- return future
70
70
}
71
71
72
72
private class Lifecycle {
73
73
private let logger : Logger
74
- private let eventLoopGroup = MultiThreadedEventLoopGroup ( numberOfThreads: 1 )
74
+ private let eventLoopGroup = MultiThreadedEventLoopGroup ( numberOfThreads: System . coreCount )
75
75
private let handler : LambdaHandler
76
76
private let max : Int
77
77
@@ -88,7 +88,7 @@ public enum Lambda {
88
88
89
89
deinit {
90
90
self . logger. info ( " lambda lifecycle deinit " )
91
- assert ( state == . shutdown)
91
+ assert ( self . state == . shutdown, " invalid state, expected shutdown " )
92
92
}
93
93
94
94
private var state : LifecycleState {
@@ -105,63 +105,60 @@ public enum Lambda {
105
105
}
106
106
}
107
107
108
- func start( ) -> EventLoopFuture < LambdaLifecycleResult > {
108
+ func start( ) -> EventLoopFuture < Int > {
109
109
self . state = . initializing
110
+ let lifecycleId = NSUUID ( ) . uuidString
111
+ let eventLoop = self . eventLoopGroup. next ( )
110
112
var logger = self . logger
111
- logger [ metadataKey: " lifecycleId " ] = . string( NSUUID ( ) . uuidString)
112
- let runner = LambdaRunner ( eventLoopGroup: eventLoopGroup, lambdaHandler: handler)
113
- let promise = self . eventLoopGroup. next ( ) . makePromise ( of: LambdaLifecycleResult . self)
114
- self . logger. info ( " lambda lifecycle starting " )
115
-
116
- runner. initialize ( logger: logger) . whenComplete { result in
117
- switch result {
118
- case . failure( let error) :
119
- promise. succeed ( . failure( error) )
120
- case . success:
121
- DispatchQueue . global ( ) . async {
122
- var lastError : Error ?
123
- var counter = 0
124
- self . state = . active // initializtion completed successfully, we're good to go!
125
- while self . state == . active, lastError == nil , self . max == 0 || counter < self . max {
126
- do {
127
- logger [ metadataKey: " lifecycleIteration " ] = " \( counter) "
128
- // blocking! per aws lambda runtime spec the polling requests are to be done one at a time
129
- switch try runner. run ( logger: logger) . wait ( ) {
130
- case . success:
131
- counter = counter + 1
132
- case . failure( let error) :
133
- if self . state == . active {
134
- lastError = error
135
- }
136
- }
137
- } catch {
138
- if self . state == . active {
139
- lastError = error
140
- }
141
- }
142
- // flush the log streams so entries are printed with a single invocation
143
- // TODO: should we suuport a flush API in swift-log's default logger?
144
- fflush ( stdout)
145
- fflush ( stderr)
146
- }
147
- promise. succeed ( lastError. map { . failure( $0) } ?? . success( counter) )
148
- }
149
- }
113
+ logger [ metadataKey: " lifecycleId " ] = . string( lifecycleId)
114
+ logger. info ( " lambda lifecycle starting " )
115
+
116
+ let runner = LambdaRunner ( eventLoop: eventLoop, lambdaHandler: handler, lifecycleId: lifecycleId)
117
+ return runner. initialize ( logger: logger) . flatMap { _ in
118
+ self . state = . active
119
+ return self . run ( logger: logger, eventLoop: eventLoop, runner: runner, count: 0 )
150
120
}
151
- return promise. futureResult
152
121
}
153
122
154
123
func stop( ) {
155
- if self . state == . stopping {
124
+ switch self . state {
125
+ case . stopping:
156
126
return self . logger. info ( " lambda lifecycle aready stopping " )
157
- }
158
- if self . state == . shutdown {
127
+ case . shutdown:
159
128
return self . logger. info ( " lambda lifecycle aready shutdown " )
129
+ default :
130
+ self . logger. info ( " lambda lifecycle stopping " )
131
+ self . state = . stopping
132
+ try ! self . eventLoopGroup. syncShutdownGracefully ( )
133
+ self . state = . shutdown
134
+ }
135
+ }
136
+
137
+ private func run( logger: Logger , eventLoop: EventLoop , runner: LambdaRunner , count: Int ) -> EventLoopFuture < Int > {
138
+ var logger = logger
139
+ logger [ metadataKey: " lifecycleIteration " ] = " \( count) "
140
+ return runner. run ( logger: logger) . flatMap { _ in
141
+ switch self . state {
142
+ case . idle, . initializing:
143
+ preconditionFailure ( " invalid run state: \( self . state) " )
144
+ case . active:
145
+ if self . max > 0 , count >= self . max {
146
+ return eventLoop. makeSucceededFuture ( count)
147
+ }
148
+ // recursive! per aws lambda runtime spec the polling requests are to be done one at a time
149
+ return self . run ( logger: logger, eventLoop: eventLoop, runner: runner, count: count + 1 )
150
+ case . stopping, . shutdown:
151
+ return eventLoop. makeSucceededFuture ( count)
152
+ }
153
+ } . flatMapErrorThrowing { error in
154
+ // if we run into errors while shutting down, we ignore them
155
+ switch self . state {
156
+ case . stopping, . shutdown:
157
+ return count
158
+ default :
159
+ throw error
160
+ }
160
161
}
161
- self . logger. info ( " lambda lifecycle stopping " )
162
- self . state = . stopping
163
- try ! self . eventLoopGroup. syncShutdownGracefully ( )
164
- self . state = . shutdown
165
162
}
166
163
}
167
164
@@ -225,6 +222,12 @@ public struct LambdaContext {
225
222
self . cognitoIdentity = cognitoIdentity
226
223
self . clientContext = clientContext
227
224
self . deadline = deadline
225
+ // mutate logger with context
226
+ var logger = logger
227
+ logger [ metadataKey: " awsRequestId " ] = . string( requestId)
228
+ if let traceId = traceId {
229
+ logger [ metadataKey: " awsTraceId " ] = . string( traceId)
230
+ }
228
231
self . logger = logger
229
232
}
230
233
}
0 commit comments