Skip to content

Commit 8d9f44b

Browse files
authored
allow custom initialization of the HandlerType of the LambdaRuntime (#310)
Motivation: Provide the flexibility for custom initialization of the HandlerType as this will often be required by higher level frameworks. Modifications: * Modify the LambdaRuntime type to accept a closure to provide the handler rather than requiring that it is provided by a static method on the Handler type * Update downstream code to use HandlerProvider * Update upstream code to support passing Handler Type of Handler Provider * Add and update tests Originally suggested and coded by @tachyonics in #308
1 parent c4f380e commit 8d9f44b

File tree

6 files changed

+359
-26
lines changed

6 files changed

+359
-26
lines changed

Sources/AWSLambdaRuntimeCore/Lambda.swift

+22-4
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public enum Lambda {
3838
configuration: LambdaConfiguration = .init(),
3939
handlerType: Handler.Type
4040
) -> Result<Int, Error> {
41-
Self.run(configuration: configuration, handlerType: CodableSimpleLambdaHandler<Handler>.self)
41+
Self.run(configuration: configuration, handlerProvider: CodableSimpleLambdaHandler<Handler>.makeHandler(context:))
4242
}
4343

4444
/// Run a Lambda defined by implementing the ``LambdaHandler`` protocol.
@@ -54,7 +54,7 @@ public enum Lambda {
5454
configuration: LambdaConfiguration = .init(),
5555
handlerType: Handler.Type
5656
) -> Result<Int, Error> {
57-
Self.run(configuration: configuration, handlerType: CodableLambdaHandler<Handler>.self)
57+
Self.run(configuration: configuration, handlerProvider: CodableLambdaHandler<Handler>.makeHandler(context:))
5858
}
5959

6060
/// Run a Lambda defined by implementing the ``EventLoopLambdaHandler`` protocol.
@@ -70,7 +70,7 @@ public enum Lambda {
7070
configuration: LambdaConfiguration = .init(),
7171
handlerType: Handler.Type
7272
) -> Result<Int, Error> {
73-
Self.run(configuration: configuration, handlerType: CodableEventLoopLambdaHandler<Handler>.self)
73+
Self.run(configuration: configuration, handlerProvider: CodableEventLoopLambdaHandler<Handler>.makeHandler(context:))
7474
}
7575

7676
/// Run a Lambda defined by implementing the ``ByteBufferLambdaHandler`` protocol.
@@ -85,6 +85,19 @@ public enum Lambda {
8585
internal static func run(
8686
configuration: LambdaConfiguration = .init(),
8787
handlerType: (some ByteBufferLambdaHandler).Type
88+
) -> Result<Int, Error> {
89+
Self.run(configuration: configuration, handlerProvider: handlerType.makeHandler(context:))
90+
}
91+
92+
/// Run a Lambda defined by implementing the ``LambdaRuntimeHandler`` protocol.
93+
/// - parameters:
94+
/// - configuration: A Lambda runtime configuration object
95+
/// - handlerProvider: A provider of the ``LambdaRuntimeHandler`` to invoke.
96+
///
97+
/// - note: This is a blocking operation that will run forever, as its lifecycle is managed by the AWS Lambda Runtime Engine.
98+
internal static func run(
99+
configuration: LambdaConfiguration = .init(),
100+
handlerProvider: @escaping (LambdaInitializationContext) -> EventLoopFuture<some LambdaRuntimeHandler>
88101
) -> Result<Int, Error> {
89102
let _run = { (configuration: LambdaConfiguration) -> Result<Int, Error> in
90103
#if swift(<5.9)
@@ -95,7 +108,12 @@ public enum Lambda {
95108

96109
var result: Result<Int, Error>!
97110
MultiThreadedEventLoopGroup.withCurrentThreadAsEventLoop { eventLoop in
98-
let runtime = LambdaRuntime(handlerType: handlerType, eventLoop: eventLoop, logger: logger, configuration: configuration)
111+
let runtime = LambdaRuntime(
112+
handlerProvider: handlerProvider,
113+
eventLoop: eventLoop,
114+
logger: logger,
115+
configuration: configuration
116+
)
99117
#if DEBUG
100118
let signalSource = trap(signal: configuration.lifecycle.stopSignal) { signal in
101119
logger.info("intercepted signal: \(signal)")

Sources/AWSLambdaRuntimeCore/LambdaHandler.swift

+23-1
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ extension EventLoopLambdaHandler {
398398
/// - note: This is a low level protocol designed to power the higher level ``EventLoopLambdaHandler`` and
399399
/// ``LambdaHandler`` based APIs.
400400
/// Most users are not expected to use this protocol.
401-
public protocol ByteBufferLambdaHandler {
401+
public protocol ByteBufferLambdaHandler: LambdaRuntimeHandler {
402402
/// Create a Lambda handler for the runtime.
403403
///
404404
/// Use this to initialize all your resources that you want to cache between invocations. This could be database
@@ -433,6 +433,28 @@ extension ByteBufferLambdaHandler {
433433
}
434434
}
435435

436+
// MARK: - LambdaRuntimeHandler
437+
438+
/// An `EventLoopFuture` based processing protocol for a Lambda that takes a `ByteBuffer` and returns
439+
/// an optional `ByteBuffer` asynchronously.
440+
///
441+
/// - note: This is a low level protocol designed to enable use cases where a frameworks initializes the
442+
/// runtime with a handler outside the normal initialization of
443+
/// ``ByteBufferLambdaHandler``, ``EventLoopLambdaHandler`` and ``LambdaHandler`` based APIs.
444+
/// Most users are not expected to use this protocol.
445+
public protocol LambdaRuntimeHandler {
446+
/// The Lambda handling method.
447+
/// Concrete Lambda handlers implement this method to provide the Lambda functionality.
448+
///
449+
/// - parameters:
450+
/// - context: Runtime ``LambdaContext``.
451+
/// - event: The event or input payload encoded as `ByteBuffer`.
452+
///
453+
/// - Returns: An `EventLoopFuture` to report the result of the Lambda back to the runtime engine.
454+
/// The `EventLoopFuture` should be completed with either a response encoded as `ByteBuffer` or an `Error`.
455+
func handle(_ buffer: ByteBuffer, context: LambdaContext) -> EventLoopFuture<ByteBuffer?>
456+
}
457+
436458
// MARK: - Other
437459

438460
@usableFromInline

Sources/AWSLambdaRuntimeCore/LambdaRunner.swift

+7-3
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@ internal final class LambdaRunner {
3333
/// Run the user provided initializer. This *must* only be called once.
3434
///
3535
/// - Returns: An `EventLoopFuture<LambdaHandler>` fulfilled with the outcome of the initialization.
36-
func initialize<Handler: ByteBufferLambdaHandler>(handlerType: Handler.Type, logger: Logger, terminator: LambdaTerminator) -> EventLoopFuture<Handler> {
36+
func initialize<Handler: LambdaRuntimeHandler>(
37+
handlerProvider: @escaping (LambdaInitializationContext) -> EventLoopFuture<Handler>,
38+
logger: Logger,
39+
terminator: LambdaTerminator
40+
) -> EventLoopFuture<Handler> {
3741
logger.debug("initializing lambda")
3842
// 1. create the handler from the factory
3943
// 2. report initialization error if one occurred
@@ -44,7 +48,7 @@ internal final class LambdaRunner {
4448
terminator: terminator
4549
)
4650

47-
return handlerType.makeHandler(context: context)
51+
return handlerProvider(context)
4852
// Hopping back to "our" EventLoop is important in case the factory returns a future
4953
// that originated from a foreign EventLoop/EventLoopGroup.
5054
// This can happen if the factory uses a library (let's say a database client) that manages its own threads/loops
@@ -59,7 +63,7 @@ internal final class LambdaRunner {
5963
}
6064
}
6165

62-
func run(handler: some ByteBufferLambdaHandler, logger: Logger) -> EventLoopFuture<Void> {
66+
func run(handler: some LambdaRuntimeHandler, logger: Logger) -> EventLoopFuture<Void> {
6367
logger.debug("lambda invocation sequence starting")
6468
// 1. request invocation from lambda runtime engine
6569
self.isGettingNextInvocation = true

Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift

+125-13
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ import NIOCore
1919
/// `LambdaRuntime` manages the Lambda process lifecycle.
2020
///
2121
/// Use this API, if you build a higher level web framework which shall be able to run inside the Lambda environment.
22-
public final class LambdaRuntime<Handler: ByteBufferLambdaHandler> {
22+
public final class LambdaRuntime<Handler: LambdaRuntimeHandler> {
2323
private let eventLoop: EventLoop
2424
private let shutdownPromise: EventLoopPromise<Int>
2525
private let logger: Logger
2626
private let configuration: LambdaConfiguration
2727

28+
private let handlerProvider: (LambdaInitializationContext) -> EventLoopFuture<Handler>
29+
2830
private var state = State.idle {
2931
willSet {
3032
self.eventLoop.assertInEventLoop()
@@ -35,18 +37,41 @@ public final class LambdaRuntime<Handler: ByteBufferLambdaHandler> {
3537
/// Create a new `LambdaRuntime`.
3638
///
3739
/// - parameters:
38-
/// - handlerType: The ``ByteBufferLambdaHandler`` type the `LambdaRuntime` shall create and manage.
40+
/// - handlerProvider: A provider of the ``Handler`` the `LambdaRuntime` will manage.
3941
/// - eventLoop: An `EventLoop` to run the Lambda on.
4042
/// - logger: A `Logger` to log the Lambda events.
41-
public convenience init(_ handlerType: Handler.Type, eventLoop: EventLoop, logger: Logger) {
42-
self.init(handlerType: handlerType, eventLoop: eventLoop, logger: logger, configuration: .init())
43+
@usableFromInline
44+
convenience init(
45+
handlerProvider: @escaping (LambdaInitializationContext) -> EventLoopFuture<Handler>,
46+
eventLoop: EventLoop,
47+
logger: Logger
48+
) {
49+
self.init(
50+
handlerProvider: handlerProvider,
51+
eventLoop: eventLoop,
52+
logger: logger,
53+
configuration: .init()
54+
)
4355
}
4456

45-
init(handlerType: Handler.Type, eventLoop: EventLoop, logger: Logger, configuration: LambdaConfiguration) {
57+
/// Create a new `LambdaRuntime`.
58+
///
59+
/// - parameters:
60+
/// - handlerProvider: A provider of the ``Handler`` the `LambdaRuntime` will manage.
61+
/// - eventLoop: An `EventLoop` to run the Lambda on.
62+
/// - logger: A `Logger` to log the Lambda events.
63+
init(
64+
handlerProvider: @escaping (LambdaInitializationContext) -> EventLoopFuture<Handler>,
65+
eventLoop: EventLoop,
66+
logger: Logger,
67+
configuration: LambdaConfiguration
68+
) {
4669
self.eventLoop = eventLoop
4770
self.shutdownPromise = eventLoop.makePromise(of: Int.self)
4871
self.logger = logger
4972
self.configuration = configuration
73+
74+
self.handlerProvider = handlerProvider
5075
}
5176

5277
deinit {
@@ -85,7 +110,7 @@ public final class LambdaRuntime<Handler: ByteBufferLambdaHandler> {
85110
let terminator = LambdaTerminator()
86111
let runner = LambdaRunner(eventLoop: self.eventLoop, configuration: self.configuration)
87112

88-
let startupFuture = runner.initialize(handlerType: Handler.self, logger: logger, terminator: terminator)
113+
let startupFuture = runner.initialize(handlerProvider: self.handlerProvider, logger: logger, terminator: terminator)
89114
startupFuture.flatMap { handler -> EventLoopFuture<Result<Int, Error>> in
90115
// after the startup future has succeeded, we have a handler that we can use
91116
// to `run` the lambda.
@@ -175,7 +200,7 @@ public final class LambdaRuntime<Handler: ByteBufferLambdaHandler> {
175200
private enum State {
176201
case idle
177202
case initializing
178-
case active(LambdaRunner, any ByteBufferLambdaHandler)
203+
case active(LambdaRunner, any LambdaRuntimeHandler)
179204
case shuttingdown
180205
case shutdown
181206

@@ -204,8 +229,16 @@ public enum LambdaRuntimeFactory {
204229
/// - eventLoop: An `EventLoop` to run the Lambda on.
205230
/// - logger: A `Logger` to log the Lambda events.
206231
@inlinable
207-
public static func makeRuntime<H: SimpleLambdaHandler>(_ handlerType: H.Type, eventLoop: any EventLoop, logger: Logger) -> LambdaRuntime<some ByteBufferLambdaHandler> {
208-
LambdaRuntime<CodableSimpleLambdaHandler<H>>(CodableSimpleLambdaHandler<H>.self, eventLoop: eventLoop, logger: logger)
232+
public static func makeRuntime<Handler: SimpleLambdaHandler>(
233+
_ handlerType: Handler.Type,
234+
eventLoop: any EventLoop,
235+
logger: Logger
236+
) -> LambdaRuntime<some ByteBufferLambdaHandler> {
237+
LambdaRuntime<CodableSimpleLambdaHandler<Handler>>(
238+
handlerProvider: CodableSimpleLambdaHandler<Handler>.makeHandler(context:),
239+
eventLoop: eventLoop,
240+
logger: logger
241+
)
209242
}
210243

211244
/// Create a new `LambdaRuntime`.
@@ -215,8 +248,16 @@ public enum LambdaRuntimeFactory {
215248
/// - eventLoop: An `EventLoop` to run the Lambda on.
216249
/// - logger: A `Logger` to log the Lambda events.
217250
@inlinable
218-
public static func makeRuntime<H: LambdaHandler>(_ handlerType: H.Type, eventLoop: any EventLoop, logger: Logger) -> LambdaRuntime<some ByteBufferLambdaHandler> {
219-
LambdaRuntime<CodableLambdaHandler<H>>(CodableLambdaHandler<H>.self, eventLoop: eventLoop, logger: logger)
251+
public static func makeRuntime<Handler: LambdaHandler>(
252+
_ handlerType: Handler.Type,
253+
eventLoop: any EventLoop,
254+
logger: Logger
255+
) -> LambdaRuntime<some LambdaRuntimeHandler> {
256+
LambdaRuntime<CodableLambdaHandler<Handler>>(
257+
handlerProvider: CodableLambdaHandler<Handler>.makeHandler(context:),
258+
eventLoop: eventLoop,
259+
logger: logger
260+
)
220261
}
221262

222263
/// Create a new `LambdaRuntime`.
@@ -226,8 +267,79 @@ public enum LambdaRuntimeFactory {
226267
/// - eventLoop: An `EventLoop` to run the Lambda on.
227268
/// - logger: A `Logger` to log the Lambda events.
228269
@inlinable
229-
public static func makeRuntime<H: EventLoopLambdaHandler>(_ handlerType: H.Type, eventLoop: any EventLoop, logger: Logger) -> LambdaRuntime<some ByteBufferLambdaHandler> {
230-
LambdaRuntime<CodableEventLoopLambdaHandler<H>>(CodableEventLoopLambdaHandler<H>.self, eventLoop: eventLoop, logger: logger)
270+
public static func makeRuntime<Handler: EventLoopLambdaHandler>(
271+
_ handlerType: Handler.Type,
272+
eventLoop: any EventLoop,
273+
logger: Logger
274+
) -> LambdaRuntime<some LambdaRuntimeHandler> {
275+
LambdaRuntime<CodableEventLoopLambdaHandler<Handler>>(
276+
handlerProvider: CodableEventLoopLambdaHandler<Handler>.makeHandler(context:),
277+
eventLoop: eventLoop,
278+
logger: logger
279+
)
280+
}
281+
282+
/// Create a new `LambdaRuntime`.
283+
///
284+
/// - parameters:
285+
/// - handlerType: The ``ByteBufferLambdaHandler`` type the `LambdaRuntime` shall create and manage.
286+
/// - eventLoop: An `EventLoop` to run the Lambda on.
287+
/// - logger: A `Logger` to log the Lambda events.
288+
@inlinable
289+
public static func makeRuntime<Handler: ByteBufferLambdaHandler>(
290+
_ handlerType: Handler.Type,
291+
eventLoop: any EventLoop,
292+
logger: Logger
293+
) -> LambdaRuntime<some LambdaRuntimeHandler> {
294+
LambdaRuntime<Handler>(
295+
handlerProvider: Handler.makeHandler(context:),
296+
eventLoop: eventLoop,
297+
logger: logger
298+
)
299+
}
300+
301+
/// Create a new `LambdaRuntime`.
302+
///
303+
/// - parameters:
304+
/// - handlerProvider: A provider of the ``Handler`` the `LambdaRuntime` will manage.
305+
/// - eventLoop: An `EventLoop` to run the Lambda on.
306+
/// - logger: A `Logger` to log the Lambda events.
307+
@inlinable
308+
public static func makeRuntime<Handler: LambdaRuntimeHandler>(
309+
handlerProvider: @escaping (LambdaInitializationContext) -> EventLoopFuture<Handler>,
310+
eventLoop: any EventLoop,
311+
logger: Logger
312+
) -> LambdaRuntime<Handler> {
313+
LambdaRuntime(
314+
handlerProvider: handlerProvider,
315+
eventLoop: eventLoop,
316+
logger: logger
317+
)
318+
}
319+
320+
/// Create a new `LambdaRuntime`.
321+
///
322+
/// - parameters:
323+
/// - handlerProvider: A provider of the ``Handler`` the `LambdaRuntime` will manage.
324+
/// - eventLoop: An `EventLoop` to run the Lambda on.
325+
/// - logger: A `Logger` to log the Lambda events.
326+
@inlinable
327+
public static func makeRuntime<Handler: LambdaRuntimeHandler>(
328+
handlerProvider: @escaping (LambdaInitializationContext) async throws -> Handler,
329+
eventLoop: any EventLoop,
330+
logger: Logger
331+
) -> LambdaRuntime<Handler> {
332+
LambdaRuntime(
333+
handlerProvider: { context in
334+
let promise = eventLoop.makePromise(of: Handler.self)
335+
promise.completeWithTask {
336+
try await handlerProvider(context)
337+
}
338+
return promise.futureResult
339+
},
340+
eventLoop: eventLoop,
341+
logger: logger
342+
)
231343
}
232344
}
233345

0 commit comments

Comments
 (0)